[quote=entanglement]De modo geral, não recomendo o uso de variáveis “static final” para valores que são na verdade configurações, como o path da aplicação. Uma bizarrice que já vi foi a seguinte (e haja paciência para explicar pro cara que ele não deveria fazer isso. )
- O fulano criou uma classe mais ou menos assim:
public static final int NUMERO_DE_CONEXOES = 20;
e em uma outra classe, ele usava esse valor.
2) A seguir, ele quis aumentar o número de conexões, sem recompilar o código todinho. Ele recompilou só a primeira classe, que alterou para:
public static final int NUMERO_DE_CONEXOES = 30;
Só que o número de conexões permaneceu em 20 na outra classe que não foi recompilada. Ele insistia que isso estava errado, e tive de descompilar o código da classe que não havia sido recompilada para convencê-lo. Mesmo assim ele ainda achava que o que ele queria fazer é o que o compilador deveria fazer, e que isso era um bug de compilação (na verdade não é; está até na especificação da linguagem, se bobear).
Quero que me expliquem por que é que ELE estava errado, e por quê.
[/quote]
Não sei se a sua pergunta era retórica, mas aqui vai.
As pessoas esquecem que o java é compilado. Existe um cara chamada javac que compila o codigo para byte-code. E um compilador que se prese tem uma fase chamada de otimização. O compilador da oracle é absurdamente otimizado. Então umas das otimizações mais simples é chamada de inline ( “em linha”), que significa que o código é copiado para onde é usado em vez de haver um chamada. Ou ele é substituido por um codigo semelhante, mas que tem menos indireção.
Um exemplo são os get/set o compiladro otimiza isso para acesso direto nas vairáveis internas do objeto e assim poupa uma invocação de método. O inline é perigoso e por isso o compilador tem que ser muito esperto.
Uma opção que o programador tem para dar a dica ao compilador que pode fazer inline é usar final. quando se diz que algo é final, o compiladro sabe que aquilo não ira mudar e pode usar o valor diretamente em vez de uma indireção.
Por exemplo:
public int perimetroQuadrado(int tamanhoLado){
final lados = 4;
return tamamhoLado * lados;
}
vai ser transformado para
public int perimetroQuadrado(int tamanhoLado){
return tamamhoLado * 4;
}
mesmo se o final estiver em um escopo fora do método.
Então e isso que acontece, o compilador simplesmente substitui (tipo search and replace) todos os usos de um static final, pelo valor. Mas apenas se o valor for inicializado “inline” ou seja, a variável é criada e atribuída em uma linha só.
Por isso a outra classe fica com o valor cravado nela, e mesmo alterando o valor original, nada acontece.
Isto não é intuitivo (é um dos puzzles do livro do Joshua Bloch excatamente por isso). Se a contante é publica e as libs são linkadas depois de compiladas, eu posso ter um jar A que define o valor da variável e um jar B que usa. Se o valor de A ficou cravado dentro do B isto quebra o contrato do late link que o java faz. Só que o detalhe é que o programador disse que era uma constante, então poque raios mudou ? Se mudou não era uma constante.
Isto leva ao conceito de “constante” no java, que tem que ser universal, como pi, ou 42. Strings de configuração, por exemplo, não são constantes porque dependem do ambiente de deploy. ( uma caso semelhante ao seu pode ser lido aqui)
Então, é uma armadilha mesmo. É preciso tem muito cuidado com o uso de final (seja static ou não), mas entenda-se que o problema não é no java é na modelagem da pessoa que forçou uma constante onde ela não existia.