Atributo STATIC com FINAL e CONSTRUTOR

Galera,

Tenho uma classe que tem um atributo static final chamado applicationPath.
Seto o valor inicial desse atributo no construtor, porém, esse construtor tem uma variavel local com o mesmo nome do atributo, ou seja, applicationPath.
Vejam o seguinte código:

[code]public class PathLocator{
final private static String applicationPath;

static{
String applicationPath;
if(xpto==xyz){
applicationPath = “abc”;
}
PathLocator.applicationPath = applicationPath;
// Não é possível utilizar “PathLocator.applicationPath” porque o atributo é final.
}

} [/code]

Porém, se trocar o nome da variável local, funciona corretamente:

[code]public class PathLocator{
final private static String applicationPath;

static{
String applicationPathAUX;
if(xpto==xyz){
applicationPathAUX = “abc”;
}
applicationPath = applicationPathAUX;
// Não é possível utilizar “PathLocator.applicationPath” porque o atributo é final.
}

} [/code]

A pergunta é:
Existe alguma forma de referenciar o atributo static com o mesmo nome de uma variável local sem utilizar a sintaxe NomeDaClasse.atributo ???

O que procuro é algo assim:

[code]public class PathLocator{
final private static String applicationPath;

static{
String applicationPath;
if(xpto==xyz){
applicationPath = “abc”;
}
this.applicationPath = applicationPath;
// o this não funciona porque o contrutor é static.
// gostaria de saber se existe algo equivalente para atributos statics
}
}
[/code]

Valeu!

A resposta simples é “não”.

[quote=CodeDeveloper]Galera,

Tenho uma classe que tem um atributo static final chamado applicationPath.
Seto o valor inicial desse atributo no construtor, porém, esse construtor tem uma variavel local com o mesmo nome do atributo, ou seja, applicationPath.
Vejam o seguinte código:
[/quote]

Não existe nenhum contrutor no seu codigo. Post o codigo do construtor para a gente entender.
Esse uso do bloco static não parece que é necessário (ou correto)

Quando há um “blank final” em um método, você só tem uma oportunidade de ajustar seu valor, e ainda por cima não pode estar dentro de um condicional.
“blank final” é o nome que se dá em Java para variáveis que são final, mas não são inicializadas na sua declaração. Vou dar um exemplo.

class TesteBlankFinal {
    private static final String blankFinal;

    static {
        String abcd;
        if (Math.random() > 0.5) abcd = "bleargh"; else abcd = "ugh";
        blankFinal = abcd;
    }

    public TesteBlankFinal() {
        System.out.println (blankFinal);
    }
    public static void main (String[] args) {
        for (int i = 0; i < 10; ++i) {
            new TesteBlankFinal();
        }
    }
}

Ele deve imprimir 10 linhas seguidas de “bleargh” ou 10 linhas seguidas de “ugh”, de acordo com o que Math.random() escolheu na hora do carregamento da classe (que é o instante onde o bloco static é invocado).

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. )

  1. 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ê.

sergiotaborda,

Até onde eu sei, o “construtor”, que na verdade é um bloco de inicialização, no caso é o static{}. Uma variável static final (quando não iniciada na declaração) só pode receber atribuição através de bloco de inicialização estático, e não pode estar dentro de um bloco condiconal (como diz o entanglement). Se está afirmação estiver errada, alguem me corrija por favor!

entanglement,

Li com atenção tudo o que você disse e concordo com o seu ponto de vista.
Resumindo, para fazer o que quero, só trocando o nome da variável local então, como no código abaixo?

[code]public class PathLocator{
final private static String applicationPath;

static{
String applicationPathAUX;
if(xpto==xyz){
applicationPathAUX = “abc”;
}
applicationPath = applicationPathAUX;
// Funciona!!
}

} [/code]

É, embora o código que você apresentou não compile corretamente, porque você está atribuindo o valor de uma variável local não inicializada para outra variável, no caso em que xpto != xyz.
De modo geral, não fique escrevendo código em que você tenha de usar um modificador de escopo para distinguir entre as variáveis de mesmo nome, mas de significados diferentes - isso é muito confuso; eu mesmo acho odioso ter de ler código de setters, que são normalmente algo como:

    this.algumaCoisa = algumaCoisa;

Isso é o costume, mas não acho muito bonito :stuck_out_tongue:

[quote=CodeDeveloper]sergiotaborda,

Até onde eu sei, o “construtor”, que na verdade é um bloco de inicialização, no caso é o static{}.
[/quote]

Tem alguma confusão ai. O Construtor é uma coisa especial diferente do bloco static

Exemplo:


public class PathLocator{  

   private static String applicationPath;  
     
   static{  
      applicationPath = "caminhoA";
   }  

   public PathLocator (){   // isto é o construtor
           applicationPath  = "caminhoB"
   }
  
   public String getPath(){
           return applicationPath;
   }

   public void setPath(String newPath){
          applicationPath newPath;
   }
} 

Repare que não coloquei o final.
Repare que o método set pode setar o valor do static e o get pode lê-lo.

As regras são:
-O bloco estático só pode acessar coisas static e corre antes de tudo o resto ( inclusive do construtor)
-O construtor é o usado quando vc usa o new
-Tudo o que tem escopo de objeto ( ou seja, não é marcado com static) pode acessar tudo o que tem escopo de classe (ou seja, que está marcado com static). O inverso não é verdade.

O que vc está tentando fazer é um padrão chamado Registry

O registry usa “propriedades globais”. Que isso que vc quer aqui. Para conseguir isso vc eleva a propriedade inteira (não apenas o private) para static assim

public final class PathRegistry{  

   private static String applicationPath;

   private PathRegistry(){   // isto é o construtor, mas ninguém vai usar
   }
  
   public static String getPath(){
           return applicationPath;
   }

   public static oid setPath(String newPath){
          applicationPath newPath;
   }
} 

Agora vc escreve a logica de decisão assim ( e não num bloco static)


PathRegistry.setPath("caminhoPadrão");

 if(xpto==xyz){    // se esta consição for verdadeira
        PathRegistry.setPath("abc"); // muda o caminho para abc
 }    


// em outro lugar qualquer da aplicação que use o caminho vc faz 


String caminho = PathRegistry.getPath();
// faz algo com o caminho

O Registry é um padrão meio em desuso e em JEE é substituido pelo JNDI ( que é o padrão Registry mais robusto , com distribuição ,etc…)
Mas pelo menos vc não tentou usar um Singleton :slight_smile: Parabéns por isso!

entanglement,

Concordo. Nesse caso não faz sentido utilizar o final mesmo.
E para que o meu exemplo ficasse realmente correto, teriamos que inicializar a variavel local conforme sua observação:[code] public class PathLocator{
final private static String applicationPath;

   static{    
      String applicationPathAUX=null;    
      if(xpto==xyz){    
         applicationPathAUX = "abc";    
      }    
      applicationPath = applicationPathAUX;    
      // Funciona!!   
   }    
    
}[/code]

Você também disse que se uma classe possui um atributo static final e outra classe o utiliza, quando o valor do atributo é alterado e apenas sua classe é recompilada, a outra classe que utiliza o atritubo ainda acessa o valor antigo??? Ao meu ver também não faz sentido (como a pessoa que você citou disse). Como elas resgatam o valor antigo, sendo que esse valor pode ser atribuido através do bloco de inicialização static, que por sua vez, pode pegar esse valor de um banco de dados ou de um arquivo property???

sergiotaborda,

Realmente utilizei o termo incorreto quando disse “construtor”. É que para manipulação de atributos estáticos na inicialização da classe, eu considero o bloco static como um construtor, por executar uma função parecida para o escopo estático, porém o nome corre é bloco de inicialização mesmo!
O padrão Registry parece ser o mais adequado nesse caso mesmo! Obrigado!

Ao final deste tópico, com minha dúvida resolvida, gostaria de confirmar as seguintes regras:

Quando um atributo é apenas final e NÃO static, ele pode ser iniciado por SOMENTE uma das formas a seguir:

  • em sua declaração;
  • construtor;
  • bloco de inicialização não static “{}”;

Quando um atributo é final e static, ele pode ser iniciado por SOMENTE uma das formas a seguir:

  • em sua declaração;
  • bloco de inicialização static “static{}”

Estas regras estão corretas? Alguém acrescenta mais sobre a atribuição de valores em atributos final ?

Em .NET (C#) o “static initialization block” do Java se chama “static constructor” mesmo.

Veja que você até entendeu o conceito - mas está misturando as terminologias.

(É o caso da “novia embarazada” em espanhol, que não é “noiva embaraçada” em português, mas sim “namorada grávida”. )

Procure por “blank final” na especificação da linguagem Java, e irá entender as regras um pouco complexas que existem para variáveis marcadas como “final”.

Estão corretas.

[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. )

  1. 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.

[quote=sergiotaborda]
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ó.[/quote]

Então no caso do valor de um atributo final ser atribuido por um arquivo de property [color=orange](mas como você disse esse não é o conceito de final, pois pode variar de ambiente ou ao longo do tempo)[/color] através do bloco de inicialização static, o comportamente será outro porque não tem como o compilador saber o valor que a variável terá , e por sua vez, não compilará o valor nas classes que o referenciam. Então mesmo sendo final, neeste caso ainda terá o link, correto?

[quote=CodeDeveloper]
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ó.[/quote]

Então no caso do valor de um atributo final ser atribuido por um arquivo de property [color=orange](mas como você disse esse não é o conceito de final, pois pode variar de ambiente ou ao longo do tempo)[/color] através do bloco de inicialização static, o comportamente será outro porque não tem como o compilador saber o valor que a variável terá , e por sua vez, não compilará o valor nas classes que o referenciam. Então mesmo sendo final, neeste caso ainda terá o link, correto?[/quote]

Sim. Como eu disse o compilador só otimiza se o valor da variável foi atribuído no momento da criação da variável. Ou seja, se foi atribuído um literal.
Se a atribuição é dinâmica de alguma forma, o compilador já não otimiza (porque ele não sabe o valor, só o runtime que sabe).

Por exemplo, ele não otimizaria isto:

public static final String path = "caminho".toString();

Porque “toString()” é uma invocação dinâmica.