java - fields - Bloques de inicialización estática
static class java (13)
Por lo que he entendido, el "bloque de inicialización estática" se utiliza para establecer valores de campo estático si no se puede hacer en una línea.
Pero no entiendo por qué necesitamos un bloqueo especial para eso. Por ejemplo, declaramos un campo como estático (sin una asignación de valor). Y luego escriba varias líneas del código que generan y asigne un valor al campo estático declarado anteriormente.
¿Por qué necesitamos estas líneas en un bloque especial como: static {...}
?
Aquí hay un ejemplo:
private static final HashMap<String, String> MAP = new HashMap<String, String>();
static {
MAP.put("banana", "honey");
MAP.put("peanut butter", "jelly");
MAP.put("rice", "beans");
}
El código en la sección (es) "estática" se ejecutará en el momento de carga de la clase, antes de que se construya cualquier instancia de la clase (y antes de que se llame a cualquier método estático). De esa manera, puede asegurarse de que los recursos de la clase estén listos para usar.
También es posible tener bloques de inicialización no estáticos. Esos actúan como extensiones al conjunto de métodos constructores definidos para la clase. Se ven como bloques de inicialización estáticos, excepto que la palabra clave "estática" se deja.
Como complemento, como dijo @Pointy.
El código en la sección (es) "estática" se ejecutará en el momento de carga de la clase, antes de que se construya cualquier instancia de la clase (y antes de que se llame a cualquier método estático).
Se supone que debe agregar System.loadLibrary("I_am_native_library")
en el bloque estático.
static{
System.loadLibrary("I_am_a_library");
}
Garantizará que no se llame a ningún método nativo antes de que la biblioteca relacionada se cargue en la memoria.
Según loadLibrary desde oracle :
Si este método se llama más de una vez con el mismo nombre de biblioteca, la segunda llamada y las posteriores se ignoran.
De manera bastante inesperada, poner System.loadLibrary no se usa para evitar que la biblioteca se cargue varias veces.
El bloque no estático:
{
// Do Something...
}
Se llama cada vez que se construye una instancia de la clase. El bloque estático solo se llama una vez , cuando se inicializa la clase, sin importar cuántos objetos de ese tipo cree.
Ejemplo:
public class Test {
static{
System.out.println("Static");
}
{
System.out.println("Non-static block");
}
public static void main(String[] args) {
Test t = new Test();
Test t2 = new Test();
}
}
Esto imprime:
Static
Non-static block
Non-static block
Entonces, tiene un campo estático (también se llama "variable de clase" porque pertenece a la clase en lugar de a una instancia de la clase; en otras palabras, se asocia a la clase en lugar de a cualquier objeto) y desea inicializarla. Entonces, si NO desea crear una instancia de esta clase y desea manipular este campo estático, puede hacerlo de tres maneras:
1- Simplemente inicialízalo cuando declares la variable:
static int x = 3;
2- Tener un bloque de inicialización estático:
static int x;
static {
x=3;
}
3- Tener un método de clase (método estático) que acceda a la variable de clase y lo inicialice: esta es la alternativa al bloque estático anterior; Puedes escribir un método estático privado:
public static int x=initializeX();
private static int initializeX(){
return 3;
}
Ahora, ¿por qué usarías bloque de inicialización estática en lugar de métodos estáticos?
Realmente depende de lo que necesites en tu programa. Pero debe saber que el bloque de inicialización estática se llama una vez y la única ventaja del método de clase es que pueden reutilizarse más tarde si necesita reinicializar la variable de clase.
Digamos que tienes una matriz compleja en tu programa. Lo inicializa (utilizando for loop por ejemplo) y luego los valores de esta matriz cambiarán a lo largo del programa, pero luego, en algún momento, desea reinicializarlo (volver al valor inicial). En este caso puedes llamar al método estático privado. En caso de que no necesite en su programa para reinicializar los valores, puede usar el bloque estático y no necesita un método estático ya que no lo usará más adelante en el programa.
Nota: los bloques estáticos se llaman en el orden en que aparecen en el código.
Ejemplo 1:
class A{
public static int a =f();
// this is a static method
private static int f(){
return 3;
}
// this is a static block
static {
a=5;
}
public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
System.out.print(A.a); // this will print 5
}
}
Ejemplo 2:
class A{
static {
a=5;
}
public static int a =f();
private static int f(){
return 3;
}
public static void main(String args[]) {
System.out.print(A.a); // this will print 3
}
}
Es un error común pensar que un bloque estático solo tiene acceso a campos estáticos. Para esto, me gustaría mostrar a continuación un fragmento de código que uso a menudo en proyectos de la vida real (copiado parcialmente de otra respuesta en un contexto ligeramente diferente):
public enum Language {
ENGLISH("eng", "en", "en_GB", "en_US"),
GERMAN("de", "ge"),
CROATIAN("hr", "cro"),
RUSSIAN("ru"),
BELGIAN("be",";-)");
static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>();
static {
for (Language l:Language.values()) {
// ignoring the case by normalizing to uppercase
ALIAS_MAP.put(l.name().toUpperCase(),l);
for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l);
}
}
static public boolean has(String value) {
// ignoring the case by normalizing to uppercase
return ALIAS_MAP.containsKey(value.toUpper());
}
static public Language fromString(String value) {
if (value == null) throw new NullPointerException("alias null");
Language l = ALIAS_MAP.get(value);
if (l == null) throw new IllegalArgumentException("Not an alias: "+value);
return l;
}
private List<String> aliases;
private Language(String... aliases) {
this.aliases = Arrays.asList(aliases);
}
}
Aquí el inicializador se usa para mantener un índice ( ALIAS_MAP
), para asignar un conjunto de alias al tipo de enumeración original. Está pensado como una extensión del método integrado valueOf proporcionado por el propio Enum
.
Como puede ver, el inicializador estático accede incluso a los aliases
campos private
. Es importante entender que el bloque static
ya tiene acceso a las instancias de valor Enum
(por ejemplo, ENGLISH
). Esto se debe a que el orden de inicialización y ejecución en el caso de los tipos Enum
, como si los campos static private
se hubieran inicializado con instancias antes de llamar a los bloques static
:
- Las constantes
Enum
que son campos estáticos implícitos. Esto requiere que el constructor Enum y los bloques de instancia, y que la inicialización de la instancia también ocurra primero. - Bloqueo
static
e inicialización de campos estáticos en el orden de ocurrencia.
Es importante tener en cuenta esta inicialización fuera de orden (constructor antes del bloque static
). También sucede cuando inicializamos campos estáticos con las instancias de manera similar a un Singleton (simplificaciones hechas):
public class Foo {
static { System.out.println("Static Block 1"); }
public static final Foo FOO = new Foo();
static { System.out.println("Static Block 2"); }
public Foo() { System.out.println("Constructor"); }
static public void main(String p[]) {
System.out.println("In Main");
new Foo();
}
}
Lo que vemos es el siguiente resultado:
Static Block 1
Constructor
Static Block 2
In Main
Constructor
Claro es que la inicialización estática en realidad puede suceder antes del constructor, e incluso después:
Simplemente accediendo a Foo en el método principal, hace que la clase se cargue y se inicie la inicialización estática. Pero como parte de la inicialización estática, nuevamente llamamos a los constructores para los campos estáticos, después de lo cual se reanuda la inicialización estática, y completa el constructor llamado desde dentro del método principal. Situación bastante compleja para la que espero que en la codificación normal no tengamos que lidiar.
Para más información sobre esto, vea el libro " Java efectiva ".
Hay algunas razones reales por las que se requiere que exista:
- inicializando
static final
miembrosstatic final
cuya inicialización podría lanzar una excepción - Inicializando miembros
static final
con valores calculados.
Las personas tienden a usar static {}
bloques static {}
como una forma conveniente de inicializar las cosas de las que depende la clase dentro del tiempo de ejecución, como garantizar que esa clase en particular esté cargada (por ejemplo, controladores JDBC). Eso se puede hacer de otras maneras; sin embargo, las dos cosas que menciono anteriormente solo se pueden hacer con una construcción como el bloque static {}
.
Primero debe comprender que las clases de su aplicación están java.class.Class
objetos java.class.Class
durante el tiempo de ejecución. Esto es cuando se ejecutan sus bloques estáticos. Así que en realidad puedes hacer esto:
public class Main {
private static int myInt;
static {
myInt = 1;
System.out.println("myInt is 1");
}
// needed only to run this class
public static void main(String[] args) {
}
}
e imprimiría "myInt is 1" a la consola. Tenga en cuenta que no he instanciado ninguna clase.
Puede ejecutar bits de código una vez para una clase antes de que se construya un objeto en los bloques estáticos.
P.ej
class A {
static int var1 = 6;
static int var2 = 9;
static int var3;
static long var4;
static Date date1;
static Date date2;
static {
date1 = new Date();
for(int cnt = 0; cnt < var2; cnt++){
var3 += var1;
}
System.out.println("End first static init: " + new Date());
}
}
Si no estuvieran en un bloque de inicialización estática, ¿dónde estarían? ¿Cómo declararía una variable que solo estaba destinada a ser local para los propósitos de la inicialización, y la distinguió de un campo? Por ejemplo, ¿cómo quieres escribir:
public class Foo {
private static final int widgets;
static {
int first = Widgets.getFirstCount();
int second = Widgets.getSecondCount();
// Imagine more complex logic here which really used first/second
widgets = first + second;
}
}
Si el first
y el second
no estuvieran en un bloque, se verían como campos. Si estuvieran en un bloque sin static
delante de él, eso contaría como un bloque de inicialización de instancia en lugar de un bloque de inicialización estático, por lo que se ejecutaría una vez por instancia construida en lugar de una vez en total.
Ahora, en este caso particular, podrías usar un método estático en su lugar:
public class Foo {
private static final int widgets = getWidgets();
static int getWidgets() {
int first = Widgets.getFirstCount();
int second = Widgets.getSecondCount();
// Imagine more complex logic here which really used first/second
return first + second;
}
}
... pero eso no funciona cuando hay varias variables que desea asignar dentro del mismo bloque, o ninguna (por ejemplo, si solo desea registrar algo o quizás inicializar una biblioteca nativa).
Si sus variables estáticas deben configurarse en tiempo de ejecución, entonces un bloque static {...}
es muy útil.
Por ejemplo, si necesita establecer el miembro estático en un valor que se almacena en un archivo de configuración o base de datos.
También es útil cuando desea agregar valores a un miembro del Map
estático ya que no puede agregar estos valores en la declaración de miembro inicial.
También es útil cuando en realidad no desea asignar el valor a nada, como cargar alguna clase solo una vez durante el tiempo de ejecución.
P.ej
static {
try {
Class.forName("com.example.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
}
}
Oye, hay otro beneficio, puedes usarlo para manejar excepciones. Imagina que getStuff()
lanza una Exception
que realmente pertenece a un bloque catch:
private static Object stuff = getStuff(); // Won''t compile: unhandled exception.
entonces un inicializador static
es útil aquí. Puede manejar la excepción allí.
Otro ejemplo es hacer cosas después que no se pueden hacer durante la asignación:
private static Properties config = new Properties();
static {
try {
config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
} catch (IOException e) {
throw new ExceptionInInitializerError("Cannot load properties file.", e);
}
}
Para volver al ejemplo del controlador JDBC, cualquier controlador JDBC decente también utiliza el inicializador static
para registrarse en el DriverManager
. También vea this y this respuesta.
Yo diría que static block
es solo azúcar sintáctica. No hay nada que puedas hacer con static
bloqueo static
y no con nada más.
Para reutilizar algunos ejemplos publicados aquí.
Este fragmento de código podría reescribirse sin utilizar un inicializador static
.
Método # 1: Con static
private static final HashMap<String, String> MAP;
static {
MAP.put("banana", "honey");
MAP.put("peanut butter", "jelly");
MAP.put("rice", "beans");
}
Método # 2: Sin static
private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
HashMap<String, String> ret = new HashMap<>();
ret.put("banana", "honey");
ret.put("peanut butter", "jelly");
ret.put("rice", "beans");
return ret;
}
el bloque estático se usa para cualquier tecnología para inicializar el miembro de datos estáticos de forma dinámica, o podemos decir que para la inicialización dinámica del miembro de datos estáticos se está utilizando el bloque estático ... Porque para la inicialización de miembros de datos no estáticos tenemos un constructor pero no tenemos Cualquier lugar donde podamos inicializar dinámicamente el miembro de datos estáticos.
Eg:-class Solution{
// static int x=10;
static int x;
static{
try{
x=System.out.println();
}
catch(Exception e){}
}
}
class Solution1{
public static void main(String a[]){
System.out.println(Solution.x);
}
}
Ahora mi int x estático se inicializará dinámicamente ..Bcoz cuando el compilador irá a Solution.x cargará la clase de solución y la carga del bloque estático en el momento de carga de la clase ... Así podremos inicializar dinámicamente ese miembro de datos estáticos ...
}