Por que variables ​​locais não são inicializadas em Java?

Houve alguma razão pela qual os projetistas do Java acharam que as variables ​​locais não deveriam receber um valor padrão? Sério, se variables ​​de instância podem receber um valor padrão, por que não podemos fazer o mesmo para variables ​​locais?

E isso também leva a problemas, conforme explicado neste comentário, em uma postagem do blog :

Bem, essa regra é mais frustrante ao tentar fechar um recurso em um bloco final. Se eu instanciar o recurso dentro de uma tentativa, mas tentar fechá-lo no final, recebo esse erro. Se eu mover a instanciação para fora da tentativa, recebo outro erro informando que ela deve estar dentro de uma tentativa.

Muito frustrante.

As variables ​​locais são declaradas principalmente para fazer algum cálculo. Portanto, é a decisão do programador para definir o valor da variável e não deve ter um valor padrão. Se o programador, por engano, não inicializou uma variável local e tomou o valor padrão, então a saída poderia ser algum valor inesperado. Portanto, no caso de variables ​​locais, o compilador pedirá ao programador para inicializar com algum valor antes de acessar a variável para evitar o uso de valores indefinidos.

O “problema” ao qual você está vinculado parece estar descrevendo esta situação:

SomeObject so; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { so.CleanUp(); // Compiler error here } 

A reclamação do comentarista é que o compilador hesita na linha na seção finally , alegando que so pode ser não inicializado. O comentário menciona outra maneira de escrever o código, provavelmente algo assim:

 // Do some work here ... SomeObject so = new SomeObject(); try { so.DoUsefulThings(); } finally { so.CleanUp(); } 

O comentarista não está satisfeito com essa solução porque o compilador diz que o código “deve estar dentro de uma tentativa”. Eu acho que isso significa que parte do código pode levantar uma exceção que não é mais tratada. Não tenho certeza. Nenhuma versão do meu código manipula quaisquer exceções, portanto, qualquer coisa relacionada à exceção na primeira versão deve funcionar da mesma forma na segunda.

De qualquer forma, esta segunda versão do código é a maneira correta de escrevê-lo. Na primeira versão, a mensagem de erro do compilador estava correta. A variável so pode ser não inicializada. Em particular, se o construtor SomeObject falhar, ele não será inicializado e, portanto, será um erro tentar chamar so.CleanUp . Sempre insira a seção try depois de adquirir o recurso que a seção finally finaliza.

O bloco tryfinally após a boot está lá apenas para proteger a instância do SomeObject , para garantir que ela seja limpa, não importa o que aconteça. Se há outras coisas que precisam ser executadas, mas elas não estão relacionadas a se a ocorrência de SomeObject foi alocada, então elas devem entrar em outro bloco tryfinally , provavelmente aquele que envolve o que eu mostrei.

Exigir que as variables ​​sejam atribuídas manualmente antes do uso não leva a problemas reais. Isso só leva a aborrecimentos menores, mas seu código será melhor para ele. Você terá variables ​​com escopo mais limitado e tryfinally blocos que não tentam proteger muito.

Se as variables ​​locais tivessem valores padrão, então so no primeiro exemplo, seria null . Isso realmente não teria resolvido nada. Em vez de obter um erro em tempo de compilation no bloco finally , você teria um NullPointerException espreita que poderia ocultar qualquer outra exceção que pudesse ocorrer na seção “Do some work here” do código. (Ou fazer exceções em seções finally encadear automaticamente para a exceção anterior? Eu não me lembro. Mesmo assim, você teria uma exceção extra no caminho do real.)

Além disso, no exemplo abaixo, uma exceção pode ter sido lançada dentro da construção de SomeObject, caso em que a variável ‘so’ seria nula e a chamada para CleanUp lançará um NullPointerException

 SomeObject so; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { so.CleanUp(); // Compiler error here } 

O que eu costumo fazer é isso:

 SomeObject so = null; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { if (so != null) { so.CleanUp(); // safe } } 

Observe que as variables ​​final de instância / membro não são inicializadas por padrão. Porque esses são finais e não podem ser alterados no programa depois. Essa é a razão pela qual o Java não fornece nenhum valor padrão para eles e força o programador a inicializá-lo.

Por outro lado, variables ​​de membros não finais podem ser alteradas posteriormente. Portanto, o compilador não permite que eles permaneçam não inicializados, precisamente, porque eles podem ser alterados mais tarde. Em relação às variables ​​locais, o escopo das variables ​​locais é muito mais restrito. O compilador sabe quando está sendo usado. Portanto, forçar o programador a inicializar a variável faz sentido.

A resposta real à sua pergunta é porque as variables ​​de método são instanciadas simplesmente adicionando um número ao ponteiro da pilha. Zerá-los seria um passo extra. Para variables ​​de class, elas são colocadas na memory inicializada no heap.

Por que não dar o passo extra? Dê um passo atrás – ninguém mencionou que o “aviso” neste caso é uma coisa muito boa.

Você nunca deve inicializar sua variável para zero ou nulo na primeira passagem (quando você está primeiro codificando). Ou atribuí-lo ao valor real ou não atribuí-lo a todos, porque se você não fizer isso, em seguida, o java pode dizer quando você realmente estragar tudo. Tome a resposta de Electric Monk como um ótimo exemplo. No primeiro caso, é incrivelmente útil que esteja dizendo que se o try () falhar porque o construtor de SomeObject emitiu uma exceção, você acabaria com um NPE no finally. Se o construtor não puder lançar uma exceção, não deverá estar na tentativa.

Este aviso é um verificador de programador ruim multi-caminho incrível que me salvou de fazer coisas estúpidas, uma vez que verifica cada caminho e garante que se você usou a variável em algum caminho, então você teve que inicializá-lo em todos os caminhos que levam até ele . Eu agora nunca inicio explicitamente variables ​​até que eu determine que é a coisa correta a se fazer.

Além disso, não é melhor dizer explicitamente “int size = 0” ao invés de “int size” e fazer com que o próximo programador descubra que você pretende que seja zero?

Por outro lado, não consigo encontrar uma única razão válida para que o compilador inicialize todas as variables ​​não inicializadas com 0.

(Pode parecer estranho postar uma nova resposta tão longa depois da pergunta, mas uma cópia apareceu.)

Para mim, a razão se resume a isto: o objective das variables ​​locais é diferente do propósito das variables ​​de instância. Variáveis ​​locais estão lá para serem usadas como parte de um cálculo; variables ​​de instância estão lá para conter o estado. Se você usar uma variável local sem atribuir um valor a ela, isso é quase certamente um erro lógico.

Dito isso, eu poderia ficar totalmente para trás exigindo que as variables ​​de instância fossem sempre explicitamente inicializadas; o erro ocorreria em qualquer construtor em que o resultado permitisse uma variável de instância não inicializada (por exemplo, não inicializada na declaração e não no construtor). Mas essa não é a decisão Gosling, et. al., levou no início dos anos 90, então aqui estamos nós. (E eu não estou dizendo que eles fizeram a binding errada.)

Eu não consegui ficar atrás de variables ​​locais, no entanto. Sim, não devemos confiar em compiladores para verificar novamente nossa lógica, e outra não, mas ainda é útil quando o compilador pega uma. 🙂

Eu acho que o objective principal era manter a similaridade com o C / C ++. No entanto, o compilador detecta e avisa sobre o uso de variables ​​não inicializadas que reduzirão o problema a um ponto mínimo. Do ponto de vista do desempenho, é um pouco mais rápido permitir que você declare variables ​​não inicializadas, pois o compilador não precisará escrever uma instrução de atribuição, mesmo se você sobrescrever o valor da variável na próxima instrução.

É mais eficiente não inicializar variables ​​e, no caso de variables ​​locais, é seguro fazê-lo, porque a boot pode ser rastreada pelo compilador.

Nos casos em que você precisa de uma variável para ser inicializada, você sempre pode fazer isso sozinho, por isso não é um problema.

O Eclipse até fornece avisos de variables ​​não inicializadas, de modo que fica bastante óbvio de qualquer maneira. Pessoalmente eu acho que é uma coisa boa que este seja o comportamento padrão, caso contrário seu aplicativo pode usar valores inesperados, e ao invés do compilador lançar um erro ele não fará nada (mas talvez dê um aviso) e então você estará coçando sua cabeça a respeito de por que certas coisas não se comportam como deveriam.

As variables ​​locais são armazenadas em uma pilha, mas as variables ​​de instância são armazenadas no heap, portanto, há algumas chances de que um valor anterior na pilha seja lido em vez de um valor padrão, como acontece no heap. Por essa razão, o jvm não permite usar uma variável local sem inicializá-lo.

A variável de instância terá valores padrão, mas as variables ​​locais não podem ter valores padrão. Como as variables ​​locais são basicamente methods / comportamento, seu objective principal é fazer algumas operações ou cálculos. Portanto, não é uma boa ideia definir valores padrão para variables ​​locais. Caso contrário, é muito difícil e demorado verificar as razões de respostas inesperadas.

A resposta é que as variables ​​de instância podem ser inicializadas no construtor de class ou em qualquer método de class, mas no caso de variables ​​locais, uma vez que você tenha definido o que permanece no método na class.

Eu poderia pensar em seguir duas razões

  1. Como a maioria das respostas disse colocando a restrição de inicializar a variável local, é garantido que a variável local seja atribuída a um valor, como o programador quer e garante que os resultados esperados sejam computados.
  2. Variáveis ​​de instância podem ser ocultas declarando variables ​​locais (mesmo nome) – para garantir o comportamento esperado, variables ​​locais são forçadas a serem iniciadas um valor. (Seria totalmente evitar isso embora)