Blocos de inicialização estática

265

Tanto quanto eu entendi, o "bloco de inicialização estática" é usado para definir valores do campo estático, se não puder ser feito em uma linha.

Mas não entendo por que precisamos de um bloco especial para isso. Por exemplo, declaramos um campo como estático (sem uma atribuição de valor). E, em seguida, escreva várias linhas do código que geram e atribuem um valor ao campo estático declarado acima.

Por que precisamos dessas linhas em um bloco especial como static {...}:?

romano
fonte
6
Feedback menor, mas ajudaria se você pudesse afirmar claramente suas suposições e, portanto, esclarecer qual resposta está correta. Quando eu ler sua pergunta, eu mis-compreendido e achava que sabia a diferença entre {...}vs static {...}. (caso em que Jon Skeet definitivamente respondeu à sua maneira melhor pergunta)
David T.
1
Esta questão não é clara; você tem os atendentes lutando e fazendo muitas conjeturas longas sobre o que você quis dizer. Que tal escrever explicitamente o exemplo de bloco de inicialização estática que você tem em mente e sua alternativa, para que as pessoas tenham algo claro para responder?
Don escotilha

Respostas:

430

O bloco não estático:

{
    // Do Something...
}

É chamado toda vez que uma instância da classe é construída. O bloco estático é chamado apenas uma vez , quando a própria classe é inicializada, não importa quantos objetos desse tipo você crie.

Exemplo:

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();
    }
}

Isso imprime:

Static
Non-static block
Non-static block
Frederik Wordenskjold
fonte
107
Por que essa é a resposta aceita? Nem sequer responde à pergunta.
Paul Bellora
43
Ele responde à pergunta: "Isso é chamado toda vez que a classe é construída. O bloco estático é chamado apenas uma vez, não importa quantos objetos desse tipo você crie".
Adam Arold
83
Para o leitor curioso, o bloco não estático é realmente copiado pelo compilador Java em todos os construtores que a classe possui ( origem ). Portanto, ainda é tarefa do construtor inicializar os campos.
Martin Andersson
2
A resposta aceita deve ser esta: stackoverflow.com/a/2420404/363573 . Esta resposta apresenta um exemplo da vida real em que você precisa de blocos estáticos.
28513 Stephan Stephan
16
Por que essa resposta está sendo subitamente votada? Você pode discordar que essa seja a resposta aceita, mas certamente não é de forma alguma errada ou enganosa. Ele está simplesmente tentando ajudar o entendimento dessas construções de linguagem com um exemplo simples.
Frederik Wordenskjold 01/12/13
133

Se eles não estivessem em um bloco de inicialização estático, onde estariam? Como você declararia uma variável que deveria ser apenas local para fins de inicialização e a distinguiria de um campo? Por exemplo, como você gostaria de escrever:

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;
    }
}

Se firste secondnão estivessem em um bloco, eles pareceriam campos. Se eles estivessem em um bloco sem a sua staticfrente, isso contaria como um bloco de inicialização da instância em vez de um bloco estático de inicialização, portanto, seria executado uma vez por instância construída e não uma vez no total.

Agora, nesse caso específico, você pode usar um método estático:

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;
    }
}

... mas isso não funciona quando há várias variáveis ​​que você deseja atribuir dentro do mesmo bloco ou nenhuma (por exemplo, se você deseja apenas registrar algo - ou talvez inicializar uma biblioteca nativa).

Jon Skeet
fonte
1
O bloco estático acontece antes que as variáveis ​​estáticas sejam atribuídas ou depois? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng
1
Estava curioso para saber se o bloco estático ocorre antes que as variáveis ​​estáticas sejam atribuídas ou depois. eg private static int widgets = 0; static{widgets = 2;}Descobriu que a atribuição '=' acontece em ordem, o que significa que a colocação '=' primeiro será atribuída primeiro. O exemplo acima vai dar 'widgets' um valor de 2. (PS não sabia que os comentários só pode ser editado em 5 min ...)
Weishi Zeng
@WeishiZeng: Sim, isso é como documentado em docs.oracle.com/javase/specs/jls/se8/html/… - ponto 9.
Jon Skeet
Mas você também não poderia usar um método estático privado que tenha exatamente o mesmo código que o bloco de inicialização estática e atribuir widgets ao método estático privado?
Zachary Kraus
1
@ Zachary: Você quer dizer retornar o valor e atribuir o resultado da chamada do método? Se sim, sim - quando você está atribuindo exatamente uma variável como resultado do bloco. Vai editar a minha resposta com detalhes em cerca de 7 horas ...
Jon Skeet
103

Aqui está um exemplo:

  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");
  }

O código nas seções "estáticas" será executado no tempo de carregamento da classe, antes que quaisquer instâncias da classe sejam construídas (e antes que qualquer método estático seja chamado de outro lugar). Dessa forma, você pode garantir que todos os recursos da classe estejam prontos para uso.

Também é possível ter blocos de inicialização não estáticos. Eles agem como extensões ao conjunto de métodos construtores definidos para a classe. Eles se parecem com blocos inicializadores estáticos, exceto que a palavra-chave "estática" é deixada de lado.

Pontudo
fonte
4
Por esse exemplo em particular, por vezes, a cinta dupla padrão é sido "abusado" :)
BalusC
Pode ser abusado, mas, por outro lado, elimina algumas bagunças e torna alguns tipos de código um pouco mais "sólidos". Programa I em Erlang para se divertir, e você ficar viciado em não precisar de variáveis locais :-)
Pointy
1
<< O código na seção "estática" será executado no tempo de carregamento da classe, antes que quaisquer instâncias da classe sejam construídas (e antes que qualquer método estático seja chamado de outro lugar). Dessa forma, você pode garantir que todos os recursos da classe estejam prontos para uso. >> (Que "Pointy" mencionado na resposta acima) é um ponto muito importante a ser observado quando se trata de execução de bloco estático.
aluno
Podemos fazer isso usando o InitializingBean no método afterPropertiesSet?
Egemen
48

Também é útil quando você realmente não deseja atribuir o valor a nada, como carregar alguma classe apenas uma vez durante o tempo de execução.

Por exemplo

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

Ei, há outro benefício, você pode usá-lo para lidar com exceções. Imagine que getStuff()aqui lança um Exceptionque realmente pertence a um bloco catch:

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

então um staticinicializador é útil aqui. Você pode lidar com a exceção lá.

Outro exemplo é fazer coisas posteriores que não podem ser feitas durante a atribuição:

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 voltar ao exemplo do driver JDBC, qualquer driver JDBC decente também faz uso do staticinicializador para se registrar no DriverManager. Veja também esta e esta resposta.

BalusC
fonte
2
Aqui está um vodu perigoso ... inicializadores estáticos são executados no método sintético clinit (), que é implicitamente sincronizado . Isso significa que a JVM adquirirá um bloqueio no arquivo de classe em questão. Isso pode levar a um conflito em ambientes multithread se duas classes tentarem carregar uma à outra e cada uma começar a carregar em um encadeamento diferente. Consulte www-01-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax
@ Ajax: Eu consideraria isso um bug no driver JDBC em questão ou no código do aplicativo responsável por carregá-lo. Normalmente, no caso de drivers JDBC decentes, desde que você o carregue apenas uma vez em todo o aplicativo durante a inicialização do aplicativo, não há nada a fazer.
precisa saber é
Certamente seria um bug, mas não inteiramente culpa do driver JDBC, no entanto. Talvez o driver tenha inocentemente seus próprios inicializadores estáticos, e talvez você inocule essa classe juntamente com outras no seu aplicativo e, oh não, algumas classes inesperadas carregam ciclicamente umas às outras, e agora os bloqueios do aplicativo. Descobri isso graças ao impasse entre java.awt.AWTEvent e sun.util.logging.PlatformLogger. Eu apenas toquei no AWTEvent para dizer para ele ficar sem cabeça, e alguma outra lib acabou carregando o PlatformLogger ... que o AWTEvent também carrega.
Ajax
1
Ambas as classes acabaram sincronizadas em threads diferentes, e minha compilação travou cerca de 1/150 execuções. Portanto, agora tenho muito mais cuidado com o carregamento de classes em blocos estáticos. No caso mencionado acima, usando um padrão de provedor diferido em que eu poderia criar uma classe de provedor provisória imediatamente (sem chance de conflito), inicializar o campo e, em seguida, quando ele for realmente acessado (em um acesso de campo não sincronizado), então eu realmente carrego as classes que podem causar o impasse.
Ajax
11

Eu diria que static blocké apenas açúcar sintático. Não há nada que você possa fazer com o staticbloco e não com mais nada.

Para reutilizar alguns exemplos publicados aqui.

Este trecho de código pode ser reescrito sem o uso do staticinicializador.

Método # 1: Com 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: Sem 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;
}
user1508893
fonte
10

Existem algumas razões reais para a sua existência:

  1. static finalmembros inicializadores cuja inicialização pode gerar uma exceção
  2. inicializando static finalmembros com valores calculados

As pessoas tendem a usar static {}blocos como uma maneira conveniente de inicializar coisas das quais a classe depende dentro do tempo de execução - como garantir que uma determinada classe seja carregada (por exemplo, drivers JDBC). Isso pode ser feito de outras maneiras; no entanto, as duas coisas que mencionei acima só podem ser feitas com uma construção como o static {}bloco.

D.Shawley
fonte
8

Você pode executar bits de código uma vez para uma classe antes que um objeto seja construído nos blocos estáticos.

Por exemplo

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());
  }
}
Pierre-Antoine LaFayette
fonte
7

É um equívoco comum pensar que um bloco estático tem apenas acesso a campos estáticos. Para isso, gostaria de mostrar abaixo um código que frequentemente uso em projetos da vida real (copiado parcialmente de outra resposta em um contexto ligeiramente 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); 
  } 
} 

Aqui, o inicializador é usado para manter um índice ( ALIAS_MAP), para mapear um conjunto de aliases de volta ao tipo de enumeração original. Ele é planejado como uma extensão do método valueOf interno fornecido pelo Enumpróprio.

Como você pode ver, o inicializador estático acessa até o privatecampo aliases. É importante entender que o staticbloco já tem acesso às Enuminstâncias de valor (por exemplo ENGLISH). Isso ocorre porque a ordem de inicialização e execução no caso de Enumtipos , como se os static privatecampos tivessem sido inicializados com instâncias antes dos staticblocos serem chamados:

  1. As Enumconstantes que são campos estáticos implícitos. Isso requer que o construtor Enum e os blocos de instância e a inicialização da instância ocorram primeiro também.
  2. static bloco e inicialização de campos estáticos na ordem da ocorrência.

staticÉ importante observar essa inicialização fora de ordem (construtor antes do bloco). Isso também acontece quando inicializamos campos estáticos com as instâncias de forma semelhante a um Singleton (simplificações feitas):

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();
  }
}

O que vemos é a seguinte saída:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

Claro é que a inicialização estática realmente pode acontecer antes do construtor e mesmo depois:

O simples acesso ao Foo no método principal faz com que a classe seja carregada e a inicialização estática inicie. Mas, como parte da inicialização estática, chamamos novamente os construtores para os campos estáticos, após o que retoma a inicialização estática e conclui o construtor chamado de dentro do método principal. Situação bastante complexa para a qual espero que na codificação normal não tenhamos que lidar.

Para mais informações, consulte o livro " Java eficaz ".

YoYo
fonte
1
Ter acesso a aliasesnão significa que o bloco estático possa acessar membros não estáticos. aliasesé acessado através dos Languagevalores retornados pelo values()método / static / . Como você mencionou, o fato de as variáveis ​​enum já estarem disponíveis nesse momento é o bit incomum - membros não estáticos de classes regulares não estariam acessíveis nessa situação.
Ignazio
O bloco estático ainda está acessando apenas campos estáticos (no caso de sua enumeração ENGLISH, GERMAN, ...) que nesse caso são objetos. Como os campos estáticos são objetos, é possível acessar o campo de instância do objeto estático.
Swami PR
1
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } O código acima não é diferente do exemplo enum e ainda permite o acesso de exemplo variável dentro do bloco estático
Swami PR
O @SwamiPR de fato compila, para minha surpresa, e eu tenho que concordar que o código não é, em princípio, diferente. Tenho que reler a especificação do Java, sinto que há algo que perdi. Boa resposta de volta, obrigado.
YoYo
@SwamiPR O problema é que deveríamos usar um Enum. É a melhor maneira de garantir que estamos apontando para instâncias singulares '- veja aqui . E para você, fiz várias atualizações.
YoYo 06/02
3

Se suas variáveis ​​estáticas precisam ser definidas em tempo de execução, um static {...}bloco é muito útil.

Por exemplo, se você precisar definir o membro estático para um valor armazenado em um arquivo de configuração ou banco de dados.

Também é útil quando você deseja adicionar valores a um Mapmembro estático, pois não é possível adicionar esses valores na declaração inicial do membro.

Marcus Leon
fonte
3

Então você tem um campo estático (também é chamado de "variável de classe" porque pertence à classe e não a uma instância da classe; em outras palavras, está associado à classe e não a qualquer objeto) e deseja inicializá-lo. Portanto, se você NÃO quiser criar uma instância dessa classe e manipular esse campo estático, poderá fazê-lo de três maneiras:

1- Apenas inicialize quando você declarar a variável:

static int x = 3;

2- Tenha um bloco de inicialização estático:

static int x;

static {
 x=3;
}

3- Ter um método de classe (método estático) que acessa a variável de classe e a inicializa: esta é a alternativa ao bloco estático acima; você pode escrever um método estático privado:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Agora, por que você usaria o bloco de inicialização estático em vez de métodos estáticos?

Realmente depende do que você precisa em seu programa. Mas você precisa saber que o bloco de inicialização estática é chamado uma vez e a única vantagem do método de classe é que eles podem ser reutilizados posteriormente, se você precisar reinicializar a variável de classe.

digamos que você tenha uma matriz complexa em seu programa. Você inicializa-la (usando para circuito por exemplo) e, em seguida, os valores desta matriz irá mudar ao longo do programa, mas, em seguida, em algum momento você quer reinicializar-lo (voltar ao valor inicial). Nesse caso, você pode chamar o método estático privado. Caso você não precise reinicializar os valores em seu programa, basta usar o bloco estático e não é necessário um método estático, pois você não o utilizará posteriormente no programa.

Nota: os blocos estáticos são chamados na ordem em que aparecem no código.

Exemplo 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
 }

}

Exemplo 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
 }

}
Randa Sbeity
fonte
0

Como suplementar, como @Pointy disse

O código nas seções "estáticas" será executado no tempo de carregamento da classe, antes que quaisquer instâncias da classe sejam construídas (e antes que qualquer método estático seja chamado de outro lugar).

É suposto adicionar System.loadLibrary("I_am_native_library")no bloco estático.

static{
    System.loadLibrary("I_am_a_library");
}

Ele garantirá que nenhum método nativo seja chamado antes que a biblioteca relacionada seja carregada na memória.

De acordo com loadLibrary da oracle :

Se esse método for chamado mais de uma vez com o mesmo nome de biblioteca, a segunda e as chamadas subseqüentes serão ignoradas.

Então, inesperadamente, colocar System.loadLibrary não é usado para evitar que a biblioteca seja carregada várias vezes.

Eugene
fonte
0

Você primeiro precisa entender que suas próprias classes de aplicativos são instanciadas em java.class.Classobjetos durante o tempo de execução. É quando seus blocos estáticos são executados. Então você pode realmente fazer isso:

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 imprimiria "myInt é 1" no console. Observe que eu não instanciei nenhuma classe.

Emmanuel Osimosu
fonte
0
static int B,H;
static boolean flag = true;
static{
    Scanner scan = new Scanner(System.in);
    B = scan.nextInt();
    scan.nextLine();
    H = scan.nextInt();

    if(B < 0 || H < 0){
        flag = false;
        System.out.println("java.lang.Exception: Breadth and height must be positive");
    } 
}
Vid
fonte
-1

O bloco estático é usado para qualquer tecnologia para inicializar o membro de dados estáticos de maneira dinâmica, ou podemos dizer que a inicialização dinâmica do membro estático está sendo usada. O bloco estático está sendo usado .. Por causa da inicialização do membro de dados não estáticos, temos construtor, mas não temos qualquer lugar onde possamos inicializar dinamicamente o membro de dados 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);
        }
        }

Agora meu int x estático será inicializado dinamicamente .. Quando o compilador for para o Solution.x, ele carregará a Classe de Solução e o bloco estático no tempo de carregamento da classe. Assim, podemos inicializar dinamicamente esse membro de dados estáticos.

}

Uma corrida
fonte