sábado, 17 de novembro de 2012

Curiosidade sobre variáveis "final" no java.

Para efeitos didáticos, estive criando uma biblioteca de cópia de objetos. A principio era para ser algo simples, mas a medida que fui desenvolvendo foram surgindo problemas que envolveram alguns estudos sobre reflection. Um deles foi em relação a manipulação de atributos "final". Lendo sobre o assunto na especificação do java aprendi um comportamento interessante.

Vejamos o seguinte código:

public class Foo{
    public static final int a = 10;
}


public class Bar{
    public static void main(String[] args){
        System.out.println(Foo.a);
    }
}

Se compilarmos e rodarmos o resultado será 10.
O que vai acontecer se alterarmos o valor da variável i para 30 e compilarmos somente a class Foo?
Se rodarmos a classe Bar veremos que ainda assim o resultado será 10.

Isso acontece por que ao compilarmos nosso código, as *constantes são substituídas pelo seus valores literais, ou seja, no bytecode da classe Bar, não existe referencia pra classe Foo, na verdade, dentro do System.out.println estará escrito o literal 10.

Podemos verificar isso executando o comando javap -c:


class Um extends java.lang.Object{
Um();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

public static void main(java.lang.String[]);
  Code:
   0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: bipush 10
   5: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
   8: return

}



*Várias regras devem ser avaliadas para considerarmos uma variável uma constante, mas resumindo, o seu valor deve ser conhecido em tempo de compilação.

Como no exemplo abaixo:



public class Valor {

 final int compilacao = 1;
 final int execucao;

 public Valor(int execucao) {
  this.execucao = execucao;
 }

 public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
  Valor valor = new Valor(2);

  System.out.println(valor.compilacao);// aqui imprime 1
  System.out.println(valor.execucao);// aqui imprime 2

  Field fieldCompilacao = Valor.class.getDeclaredField("compilacao");
  Field fieldExecucao = Valor.class.getDeclaredField("execucao");

  fieldCompilacao.setAccessible(true);
  fieldExecucao.setAccessible(true);

  fieldCompilacao.set(valor, 10);
  fieldExecucao.set(valor, 20);

  System.out.println(valor.compilacao);// aqui deveria imprimir 10 mas imprime 1
  System.out.println(valor.execucao);// aqui imprime 20

 }
}


Perceba que o valor da variável "compilacao" é conhecido em tempo de compilação, portanto, no bytecode ela será substituída por seu valor literal, já a variável "execucao" só é conhecida em tempo de execução (no momento de criação do objeto).

Com isso em mente percebi o que estava errando na minha biblioteca de cópia de objetos. Existe uma grande diferença entre algo constante e algo imutável. Para efeitos de testes criei algumas classes com atributos final e sem perceber inicializei esses valores "inline", tornando seu valores conhecidos em tempo de compilação, portanto, constantes. Não vejo muito sentido alterar valores de constantes, a final, são constantes!!

Agora devo trabalhar nesse sentido, copia de objetos, sejam eles imutáveis ou não, deixando de lado as constantes.


Quem quiser conhecer a biblioteca, está no link abaixo:

https://github.com/geraldox100/copyobject

Referências:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.9
http://www.javaworld.com/javaqa/2003-03/02-qa-0328-constant.html
http://www.coderanch.com/t/454384/java/java/compile-time-constant