polimorfismo modificador metodos metodo constantes clase abstracta java interface override final

modificador - ¿Por qué las constantes finales en Java pueden ser anuladas?



polimorfismo java (6)

Considere la siguiente interfaz en Java:

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

Y la siguiente clase:

public class A implements I { public String KEY = "b"; public String getKey() { return KEY; } }

¿Por qué es posible que la clase A venga y anule la constante final de la interfaz I?

Pruébelo usted mismo:

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


No debe acceder a su constante de esta manera, use la referencia estática en su lugar:

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


Lo estás ocultando, es una característica de "Alcance". Cada vez que se encuentre en un alcance menor, puede redefinir todas las variables que le gusten y las variables de ámbito externo se "sombrearán".

Por cierto, puede ampliar el alcance si lo desea:

public class A implements I { public String KEY = "b"; public String getKey() { String KEY = "c"; return KEY; } }

Ahora KEY devolverá "c";

Editado porque el original fue absorbido por la relectura.


Los campos estáticos y los métodos se adjuntan a la clase / interfaz que los declara (aunque las interfaces no pueden declarar métodos estáticos ya que son clases totalmente abstractas que deben implementarse).

Entonces, si tiene una interfaz con un static público (vartype) (varname), ese campo está adjunto a esa interfaz.

Si tiene una clase que implementa esa interfaz, el truco del compilador transforma (this.) Varname en InterfaceName.varname. Pero, si su clase redefine varname, se asociará una nueva constante llamada varname a su clase, y el compilador sabe ahora traducir (this.) Varname a NewClass.varname. Lo mismo aplica para los métodos: si la nueva clase no redefine el método, (this.) MethodName se traduce a SuperClass.methodName; de ​​lo contrario, (this.) MethodName se traduce a CurrentClass.methodName.

Es por esto que encontrará la advertencia "se debe acceder al campo / método x de una manera estática". El compilador le dice que, aunque puede usar el truco, preferiría que use ClassName.method / fieldName, porque es más explícito para fines de legibilidad.


Parece que tu clase simplemente está ocultando la variable, no sobrescribiéndola:

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); } }

Esto imprimirá "B" y "A", como descubriste. Incluso puede asignarlo, ya que la variable A.KEY no se define como final.

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

Pero -

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 } }


Como una consideración de diseño,

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

Los métodos estáticos siempre devuelven la clave principal.

public class A implements I { public String KEY = "b"; public String getKey() { return KEY; // returns "b" } public static String getParentKey(){ return KEY; // returns "a" } }

Justo como Jom lo ha notado. El diseño de métodos estáticos que utilizan miembros de la interfaz redefinidos podría ser un gran problema. En general, trate de evitar usar el mismo nombre para la constante.


A pesar de que está ocultando la variable, es bastante interesante saber que puede cambiar los campos finales en Java, como puede leer aquí :

Java 5 - "final" ya no es definitivo

Narve Saetre de Machina Networks en Noruega me envió una nota ayer, mencionando que era una pena que pudiéramos cambiar el mango a una matriz final. Lo malentendí y comencé pacientemente a explicar que no podíamos hacer una matriz constante, y que no había forma de proteger el contenido de una matriz. "No", dijo él, "podemos cambiar un mango final usando la reflexión".

Probé el código de muestra de Narve, e increíblemente, Java 5 me permitió modificar un identificador final, incluso un identificador para un campo primitivo. Sabía que solía permitirse en algún momento, pero luego no se permitió, así que realicé algunas pruebas con versiones anteriores de Java. Primero, necesitamos una clase con campos finales:

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

En JDK 1.1.x, no pudimos acceder a los campos privados mediante la reflexión. Sin embargo, podríamos crear otra Persona con campos públicos, luego compilar nuestra clase con eso y cambiar las clases de Persona. No había comprobación de acceso en tiempo de ejecución si estábamos corriendo contra una clase diferente a la que compilamos. Sin embargo, no pudimos revincular los campos finales en tiempo de ejecución utilizando el intercambio de clases o la reflexión.

El JDK 1.1.8 JavaDocs para java.lang.reflect.Field tenía lo siguiente para decir:

  • Si este objeto Field impone el control de acceso al lenguaje Java y el campo subyacente es inaccesible, el método arroja una IllegalAccessException.
  • Si el campo subyacente es final, el método arroja una IllegalAccessException.

JDK 1.2.x

En JDK 1.2.x, esto cambió un poco. Ahora podemos hacer que los campos privados sean accesibles con el método setAccessible (verdadero). El acceso a los campos ahora se verificó en el tiempo de ejecución, por lo que no pudimos usar el truco de intercambio de clases para acceder a los campos privados. Sin embargo, ahora podríamos volver a vincular los campos finales. Mira 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); } }

Cuando ejecuté esto en JDK 1.2.2_014, obtuve el siguiente 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 una primitiva en el momento de la declaración, el valor está en línea, si el tipo es primitivo o una cadena.

JDK 1.3.xy 1.4.x

En JDK 1.3.x, Sun ajustó un poco el acceso y nos impidió modificar un campo final con reflejo. Este fue también el caso con JDK 1.4.x. Si intentáramos ejecutar la clase FinalFieldChange para volver a enlazar los campos finales en tiempo de ejecución mediante el uso de la reflexión, obtendríamos:

versión java "1.3.1_12": cadena de excepciones "principal" IllegalAccessException: el campo es final en java.lang.reflect.Field.set (método nativo) en FinalFieldChange.change (FinalFieldChange.java:8) en FinalFieldChange.main (FinalFieldChange. java: 12)

java version "1.4.2_05" Exception thread "main" IllegalAccessException: El campo es final en java.lang.reflect.Field.set (Field.java:519) en FinalFieldChange.change (FinalFieldChange.java:8) en FinalFieldChange.main ( FinalFieldChange.java:12)

JDK 5.x

Ahora llegamos a JDK 5.x. La clase FinalFieldChange tiene el mismo resultado que en 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

reflexión, esperaba que un error se hubiera infiltrado en el JDK. Sin embargo, ambos sentimos que era poco probable, especialmente un error tan fundamental. Después de algunas búsquedas, encontré el JSR-133: modelo de memoria de Java y especificación de subprocesos. La mayoría de las especificaciones son difíciles de leer, y me recuerdan mis días en la universidad (solía escribir así ;-) Sin embargo, JSR-133 es tan importante que debería ser una lectura obligatoria para todos los programadores de Java. (Buena suerte)

Comience con el capítulo 9 Semántica de campo final, en la página 25. Específicamente, lea la sección 9.1.1 Modificación posterior a la construcción de los campos finales. Tiene sentido permitir actualizaciones a los campos finales. Por ejemplo, podríamos relajar el requisito de tener campos no finales en JDO.

Si leemos la sección 9.1.1 con cuidado, vemos que solo debemos modificar los campos finales como parte de nuestro proceso de construcción. El caso de uso es donde deserializamos un objeto, y luego una vez que hemos construido el objeto, iniciamos los campos finales, antes de pasarlo. Una vez que hemos hecho que el objeto esté disponible para otro hilo, no deberíamos cambiar los campos finales usando el reflejo. El resultado no sería predecible

Incluso dice esto: si un campo final se inicializa en una constante de tiempo de compilación en la declaración de campo, es posible que no se observen los cambios en el campo final, ya que los usos de ese campo final se reemplazan en tiempo de compilación con la constante de tiempo de compilación. Esto explica por qué nuestro campo iq se mantiene igual, pero el país cambia.

Extrañamente, JDK 5 difiere ligeramente de JDK 1.2.x, en que no se puede modificar un 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(); } }

Cuando ejecutamos esto con JDK 1.2.x y JDK 5.x, obtenemos el siguiente resultado:

versión java "1.2.2_014": stringValue = valor original objValue = new Value

Versión java "1.5.0" Subproceso de excepción "principal" IllegalAccessException: campo final en java.lang.reflect.Field.set (Field.java:656) en FinalStaticFieldChange.changeStaticField (12) en FinalStaticFieldChange.main (16)

Entonces, JDK 5 es como JDK 1.2.x, simplemente diferente?

Conclusión

¿Sabes cuándo se lanzó JDK 1.3.0? Luché para descubrirlo, así que lo descargué e instalé. El archivo readme.txt tiene la fecha 2000/06/02 13:10. Por lo tanto, es más de 4 años (Dios mío, se siente como ayer). ¡JDK 1.3.0 fue lanzado varios meses antes de que comenzara a escribir The Java (tm) Specialists ''Newsletter! Creo que sería seguro decir que muy pocos desarrolladores de Java pueden recordar los detalles de pre-JDK1.3.0. Ahh, ¡la nostalgia ya no es lo que solía ser! ¿Recuerdas haber ejecutado Java por primera vez y obtener este error: "No se pueden inicializar los hilos: no se puede encontrar la clase java / lang / Thread"?