Pergunta

Considere a seguinte interface em Java:

public interface I {
    public final String KEY = "a";
}

E a seguinte classe:

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY;
    }
}

Por que é possível para a classe A para vir e substituir interface I constante de final?

Tente você mesmo:

A a = new A();
String s = a.getKey(); // returns "b"!!!
Foi útil?

Solução

Apesar do fato de que você está sombreando a variável é bastante interessante saber que você pode alterar campos finais em java como você pode ler aqui :

Java 5 - "final" não é mais final

Narve Saetre de Machina Networks na Noruega enviou-me uma nota ontem, mencionar que era uma pena que poderia mudar o identificador para um matriz final. Eu mal dele, e começou explicando pacientemente que não poderíamos fazer uma constante de matriz, e que não havia nenhuma maneira de proteger o conteúdo de uma matriz. "Não", disse ele, "nós podemos mudar um punho final usando reflexão ".

Eu tentei código de exemplo de Narve, e incrivelmente, Java 5 me permitiu modificar uma alça final, mesmo um identificador para um campo primitivo! eu sabia ele é utilizado para ser permitido em algum ponto, mas que, em seguida, foi anulado, então eu corri alguns testes com versões mais antigas do Java. Em primeiro lugar, precisamos de um classe com campos finais:

public class Person {
  private final String name;
  private final int age;
  private final int iq = 110;
  private final Object country = "South Africa";

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String toString() {
    return name + ", " + age + " of IQ=" + iq + " from " + country;
  }
}

JDK 1.1.x

Em JDK 1.1.x, não foram capazes de acessar campos privados usando reflexão. Poderíamos, no entanto, criar uma outra pessoa com público campos, em seguida, compilar a nossa classe contra isso, e trocar a Pessoa Aulas. Não havia acesso a verificação em tempo de execução se estávamos correndo contra uma classe diferente do que nós compilamos contra. No entanto, não poderíamos religar campos finais em tempo de execução usando troca de classe ou reflexão.

Os JDK 1.1.8 JavaDocs para java.lang.reflect.Field teve a seguinte a dizer:

  • Se este objeto Campo reforça o controle de acesso a linguagem Java, e o campo subjacente é inacessível, o método lança um IllegalAccessException.
  • Se o campo subjacente é final, o método lança uma IllegalAccessException.

JDK 1.2.x

Em JDK 1.2.x, isso mudou um pouco. Nós poderíamos agora fazer campos privados acessível com o método setAccessible (verdadeiro). Acesso de campos foi agora verificada em tempo de execução, de modo que não poderia usar o truque classe troca para campos privados de acesso. No entanto, poderíamos agora, de repente religar finais Campos! Olhar para este código:

import java.lang.reflect.Field;

public class FinalFieldChange {
  private static void change(Person p, String name, Object value)
      throws NoSuchFieldException, IllegalAccessException {
    Field firstNameField = Person.class.getDeclaredField(name);
    firstNameField.setAccessible(true);
    firstNameField.set(p, value);
  }
  public static void main(String[] args) throws Exception {
    Person heinz = new Person("Heinz Kabutz", 32);
    change(heinz, "name", "Ng Keng Yap");
    change(heinz, "age", new Integer(27));
    change(heinz, "iq", new Integer(150));
    change(heinz, "country", "Malaysia");
    System.out.println(heinz);
  }
}

Quando eu corri isso em JDK 1.2.2_014, eu tenho o seguinte resultado:

Ng Keng Yap, 27 of IQ=110 from Malaysia    Note, no exceptions, no complaints, and an incorrect IQ result. It seems that if we set a

campo final de um primitivo em tempo de declaração, o valor é embutido, se o tipo é primitivo ou uma String.

JDK 1.3.xe 1.4.x

Em JDK 1.3.x, Sun reforçou-se o acesso um pouco, e nos impediu de modificando um campo final com reflexão. Este também foi o caso com JDK 1.4.x. Se tentássemos correr a classe FinalFieldChange religar os campos finais em tempo de execução usando reflexão, teríamos:

java version "1.3.1_12": rosca Exception "main" IllegalAccessException: campo é definitiva em java.lang.reflect.Field.set (Método Nativo) em FinalFieldChange.change (FinalFieldChange.java:8) em FinalFieldChange.main (FinalFieldChange.java:12)

java version "1.4.2_05" thread Exception "main" IllegalAccessException: Campo é final em java.lang.reflect.Field.set (Field.java:519) em FinalFieldChange.change (FinalFieldChange.java:8) em FinalFieldChange.main (FinalFieldChange.java:12)

JDK 5.x

Agora chegamos ao JDK 5.x. A classe FinalFieldChange tem a mesma saída como no JDK 1.2.x:

Ng Keng Yap, 27 of IQ=110 from Malaysia    When Narve Saetre mailed me that he managed to change a final field in JDK 5 using

reflexão, eu estava esperando que um bug surgiu na JDK. Contudo, que ambos sentida que seja improvável, especialmente um erro tão fundamental. Depois de algumas pesquisas, eu encontrei a JSR-133: Java Modelo de Memória e Passe Specification. A maior parte da especificação é de leitura difícil, elembra-me dos meus dias de universidade (eu usei para escrever assim ;-) No entanto, JSR-133 é tão importante que deveria ser leitura obrigatória para todos os programadores Java. (Boa sorte)

Comece com o capítulo 9 final campo Semântica, na página 25. Especificamente, Leia a seção 9.1.1 Pós-Construção Modificação de Final Fields. isto faz sentido para permitir atualizações para campos finais. Por exemplo, poderíamos relaxar a exigência de campos não-finais na JDO.

Se lermos seção 9.1.1 com cuidado, vemos que só deve modificar campos finais, como parte de nosso processo de construção. O caso de uso é onde desserializar um objeto e, em seguida, uma vez que tenhamos construído o objeto, nós inicializar os campos finais, antes de passá-lo. Quando nós ter feito o objeto disponível para outro segmento, não devemos mudar campos finais usando reflexão. O resultado não seria previsível.

Ele ainda diz o seguinte: Se um campo final é inicializada com um tempo de compilação constante na declaração do campo, muda para o campo final não pode ser observado, uma vez que as utilizações de campo final são substituídos na compilação tempo com a constante de tempo de compilação. Isso explica por que o nosso campo de QI permanece a mesma, mas as mudanças país.

Estranhamente, JDK 5 difere ligeiramente do JDK 1.2.x, em que você não pode modificar um campo final estático.

import java.lang.reflect.Field;

public class FinalStaticFieldChange {
  /** Static fields of type String or primitive would get inlined */
  private static final String stringValue = "original value";
  private static final Object objValue = stringValue;

  private static void changeStaticField(String name)
      throws NoSuchFieldException, IllegalAccessException {
    Field statFinField = FinalStaticFieldChange.class.getDeclaredField(name);
    statFinField.setAccessible(true);
    statFinField.set(null, "new Value");
  }

  public static void main(String[] args) throws Exception {
    changeStaticField("stringValue");
    changeStaticField("objValue");
    System.out.println("stringValue = " + stringValue);
    System.out.println("objValue = " + objValue);
    System.out.println();
  }
}

Quando executar este com JDK 1.2.x e JDK 5.x, obtemos a seguinte saída:

versão java "1.2.2_014": stringValue = valor original objValue = new Valor

java version "1.5.0" thread Exception "principal" IllegalAccessException: O campo é final java.lang.reflect.Field.set (Field.java:656) em FinalStaticFieldChange.changeStaticField (12) a FinalStaticFieldChange.main (16)

Assim, o JDK 5 é como JDK 1.2.x, apenas diferente?

Conclusão

Você sabe quando o JDK 1.3.0 foi lançado? Eu lutei para descobrir, então eu baixado e instalado. O arquivo readme.txt tem a data 2000/06/02 13:10. Assim, é mais de 4 anos de idade (bondade mim, parece que foi ontem). JDK 1.3.0 foi lançado alguns meses antes de eu começou a escrever O Java Newsletter (TM) Especialistas! Eu acho que seria ser seguro dizer que muito poucos desenvolvedores Java pode se lembrar dos detalhes de pré-JDK1.3.0. Ahh, nostalgia não é o que costumava ser! Você lembre-se executando o Java pela primeira vez e recebendo este erro: "Não é possível inicializar tópicos: não é possível encontrar a classe java / lang / Fio"?

Outras dicas

Você está escondendo isso, é uma característica do "Scope". Toda vez que você está em um escopo menor, você pode redefinir todas as variáveis ??que você gosta e as variáveis ??escopo externo será "sombreado"

A propósito, você pode alcance-lo novamente se você gosta:

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        String KEY = "c";
        return KEY;
    }
}

Agora CHAVE voltará "c";

Editado porque o original sugado mediante re-leitura.

Parece que a sua classe é simplesmente esconder a variável, e não substituí-lo:

public class A implements I {
    public String   KEY = "B";

    public static void main(String args[])
    {
        A t = new A();
        System.out.println(t.KEY);
        System.out.println(((I) t).KEY);
    }
}

Isto irá imprimir "B" e "A", como você o encontrou. Você pode até atribuir-lhe, como a variável A.KEY não está definido como final.

 A.KEY="C" <-- this compiles.

Mas -

public class C implements I{

    public static void main (String args[])
    {
        C t = new C();
        c.KEY="V"; <--- compiler error ! can't assign to final

    }
}

Você não deve acessar a sua constante, desta forma, usar a referência estática em vez disso:

I.KEY //returns "a"
B.KEY //returns "b"

Como consideração design,

public interface I {
    public final String KEY = "a";
}

Os métodos estáticos sempre retorna a chave pai.

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY; // returns "b"
    }

    public static String getParentKey(){
        return KEY; // returns "a"
    }
}

Assim como Jom tem notado. O projeto de métodos estáticos usando membros de interface definido pelo re poderia ser um problema pesado. Em geral, tente evitar usar o mesmo nome para a constante.

campos e métodos estáticos estão ligados à classe / interface de declará-los (embora interfaces não pode declarar métodos estáticos como eles são classes totalmente abstratas que precisam ser implementadas).

Então, se você tem uma interface com um public static (vartype) (varname), que o campo está ligado a essa interface.

Se você tem uma classe implementar essa interface, as transformações truque compilador (isso). Varname em InterfaceName.varname. Mas, se sua classe redefine varname, um varname nova constante chamada está ligado à sua classe, e o compilador sabe agora traduzir (isso). Varname em NewClass.varname. O mesmo aplica-se para os métodos de:., Se a nova classe não re-definir o método, (. Este) nomeMétodo é traduzida em SuperClass.methodName, de outra forma, (. Este) nomeMétodo é traduzida em CurrentClass.methodName

É por isso que você vai encontrar o aviso "x campo / método deve ser acessado de forma estática". O compilador está lhe dizendo que, embora possa usar o truque, ele preferiria que você usou ClassName.method / fieldName, porque é mais explícito para fins de legibilidade.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top