Devo usar blocos inicializadores em Java?

16

Recentemente, deparei com uma construção Java que nunca tinha visto antes e estava me perguntando se deveria usá-la. Parece ser chamado de blocos inicializadores .

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

O bloco de código será copiado em cada construtor, ou seja, se você tiver vários construtores, não precisará reescrever o código.

No entanto, vejo três desvantagens principais usando esta sintaxe:

  1. É um dos poucos casos em Java em que a ordem do seu código é importante, pois você pode definir vários blocos de código e eles serão executados na ordem em que são gravados. Isso me parece prejudicial, pois simplesmente alterar a ordem dos blocos de código realmente mudará o código.
  2. Eu realmente não vejo nenhum benefício ao usá-lo. Na maioria dos casos, os construtores se chamam com alguns valores predefinidos. Mesmo que não seja esse o caso, o código pode ser simplesmente colocado em um método privado e chamado de cada construtor.
  3. Isso reduz a legibilidade, pois você pode colocar o bloco no final da classe e o construtor normalmente está no início da classe. É bastante contra-intuitivo olhar para uma parte completamente diferente de um arquivo de código, se você não espera que isso seja necessário.

Se minhas afirmações acima são verdadeiras, por que (e quando) essa construção de linguagem foi introduzida? Existem casos de uso legítimos?

Restabelecer Monica - dirkk
fonte
3
O exemplo que você postou não inclui nada parecido com um bloco de inicialização.
Simon B
6
O @SimonBarker olha novamente - o { doStuff(); }nível de classe é um bloco de inicialização.
amon
@SimonBarker O bloco de código que está cercandodoStuff()
Reintegrar Monica - dirkk
2
"[S] implica que mudar a ordem dos blocos de código realmente mudará o código." E como isso é diferente de alterar a ordem dos inicializadores variáveis ​​ou linhas de código individuais? Se não houver dependências, não ocorrerá nenhum dano e, se houver dependências, colocar as dependências fora de ordem é o mesmo que as dependências de falta de ordem para linhas de código individuais. Só porque Java permite que você se refira a métodos e classes antes de serem definidos, não significa que o código dependente de ordem seja raro em Java.
JAB

Respostas:

20

Existem dois casos em que eu uso blocos inicializadores.

O primeiro é para inicializar os membros finais. Em Java, você pode inicializar um membro final ou alinhado com a declaração ou inicializá-lo no construtor. Em um método, é proibido atribuir a um membro final.

Isso é válido:

final int val = 2;

Isso também é válido:

final int val;

MyClass() {
    val = 2;
}

Isto é inválido:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

Se você tiver vários construtores e se não puder inicializar um membro final embutido (porque a lógica de inicialização é muito complexa) ou se os construtores não puderem se chamar, copie / cole o código de inicialização ou use um bloco inicializador.

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

O outro caso de uso que eu tenho para blocos inicializadores é a construção de pequenas estruturas de dados auxiliares. Declaro um membro e coloco valores logo após suas declarações em seu próprio bloco inicializador.

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}
barjak
fonte
Não é a chamada de método inválida. É o código dentro do método init que é inválido. Somente construtores e blocos de inicializador podem atribuir a uma variável de membro final, portanto, a atribuição no init não será compilada.
Barjak
Seu quarto bloco de código não é compilado. Os blocos do inicializador são executados antes de todos os construtores, portanto squareVal = val * val, reclamam sobre o acesso a valores não inicializados. Blocos inicializadores não podem depender de nenhum argumento passado ao construtor. A solução usual que eu já vi para esse tipo de problema é definir um único construtor de "base" com a lógica complexa e definir todos os outros construtores em termos desse. De fato, a maioria dos usos de inicializadores de instância pode ser substituída por esse padrão.
Malnormalulo 03/07
11

Em geral, não use blocos inicializadores não estáticos (e talvez evite também estáticos).

Sintaxe Confusa

Olhando para esta pergunta, existem 3 respostas, mas você enganou 4 pessoas com esta sintaxe. Eu era um deles e escrevo Java há 16 anos! Claramente, a sintaxe é potencialmente propensa a erros! Eu ficaria longe disso.

Construtores telescópicos

Para coisas realmente simples, você pode usar construtores "telescópicos" para evitar essa confusão:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

Padrão do Construtor

Se você precisar fazerStuff () no final de cada construtor ou outra inicialização sofisticada, talvez um padrão de construtor seja o melhor. Josh Bloch lista várias razões pelas quais os construtores são uma boa idéia. Os construtores demoram um pouco para escrever, mas adequadamente escritos, eles são uma alegria de usar.

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

Loops de Inicializador Estático

Eu costumava usar muito inicializadores estáticos , mas ocasionalmente encontrava loops em que duas classes dependiam dos blocos de inicializadores estáticos um do outro serem chamados antes que a classe pudesse ser totalmente carregada. Isso produziu uma mensagem de erro "falha ao carregar a classe" ou uma vaga igualmente semelhante. Eu tive que comparar arquivos com a última versão de trabalho conhecida no controle de origem para descobrir qual era o problema. Nada divertido.

Inicialização lenta

Talvez os inicializadores estáticos sejam bons por razões de desempenho quando funcionam e não são muito confusos. Mas, em geral, estou preferindo a inicialização lenta a inicializadores estáticos atualmente. Está claro o que eles fazem, ainda não encontrei um bug de carregamento de classe e eles funcionam em mais situações de inicialização do que os blocos de inicialização.

Definição de dados

Em vez de inicialização estática para a construção de estruturas de dados (compare com os exemplos nas outras respostas), agora uso as funções auxiliares de definição de dados imutáveis ​​do Paguro :

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

Conculsion

No início do Java, os blocos inicializadores eram a única maneira de fazer algumas coisas, mas agora eles são confusos, propensos a erros e, na maioria dos casos, foram substituídos por alternativas melhores (detalhadas acima). É interessante saber sobre os blocos de inicializadores, caso você os veja em código legado ou eles surjam em um teste, mas se eu estivesse revisando o código e vi um no novo código, pediria que justificasse por que nenhum dos as alternativas acima foram adequadas antes de dar o polegar para cima ao código.

GlenPeterson
fonte
3

Além da inicialização de uma variável de instância declarada como final(consulte a resposta do barjak ), eu também mencionaria o staticbloco de inicialização.

Você pode usá-los como uma espécie de "contratante estático".

Dessa forma, você pode fazer inicializações complexas em uma variável estática na primeira vez em que a classe é referenciada.

Aqui está um exemplo inspirado no exemplo de barjak:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}
C.Champanhe
fonte
1

No que diz respeito aos blocos inicializadores não estáticos, sua função básica é atuar como um construtor padrão em classes anônimas. Esse é basicamente o único direito deles de existir.

Nico
fonte
0

Eu concordo totalmente com as declarações 1, 2, 3. Também nunca uso inicializadores de bloco por esses motivos e não sei por que ele existe em Java.

No entanto, sou forçado a usar o inicializador de bloco estático em um caso: quando preciso instanciar um campo estático cujo construtor pode gerar uma exceção verificada.

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

Mas, em vez disso, você deve fazer:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

Acho esse idioma muito feio (também impede que você marque contextcomo final), mas é a única maneira suportada pelo Java para inicializar esses campos.

Visto
fonte
Acho que se você definir context = null;seu bloco de captura, poderá declarar o contexto como final.
GlenPeterson
@GlenPeterson Eu tentei, mas ele não compila:The final field context may already have been assigned
Manchou
oops! Aposto que você pode tornar seu contexto final se você introduzir uma variável local dentro do bloco estático:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson