01 Conocimientos básicos
En pocas palabras, una clase inmutable es una clase definida con la palabra clave final, de la siguiente manera:
final class ClassName{
}
La palabra clave familiar final se usa generalmente para definir constantes. Los métodos y variables declarados con final tienen las siguientes características:
1. No se permite anular los métodos declarados como definitivos.
02 Un ejemplo milagroso
Observa el siguiente ejemplo:
public class ExplorationJDKSource {
public static void main(String[] args) {
System.out.println(new A());
}
}
//类A没有定义任何的成员
class A {
}
Ejecutar el código produce el siguiente resultado:
A@4eec7777
¿Por qué existe tal situación? La razón de esto es que, de manera predeterminada, cuando un objeto se imprime directamente como una cadena , se llama al método ` del objeto toString()`
para obtener la representación de la cadena.
En este ejemplo, la clase A
no define ningún miembro, por lo que hereda la toString()`
implementación del método predeterminado. El método predeterminado ` toString()`
devuelve una cadena que consiste en el nombre de la clase y el código hash del objeto . Por lo tanto, la "A" en la cadena de salida "A@4eec7777" es el nombre de la clase y "4eec7777" es el código hash del objeto.
Después de descompilar el archivo .class compilado, verá el siguiente resultado:
PS E:\ProgrammingFiles\Java\CompilerLab\out\production\CompilerLab> javap -c .\ExplorationJDKSource.class
Compiled from "ExplorationJDKSource.java"
public class ExplorationJDKSource {
public ExplorationJDKSource();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
public ExplorationJDKSource();
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 #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #13 // class A
6: dup
7: invokespecial #15 // Method A."<init>":()V
10: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
13: return
}
A partir de la salida descompilada, podemos ver que el código en realidad llama al método ` public void println(Object x) ` en el JDK. Vea el código fuente de la siguiente manera:
public void println(Object x) {
String s = String.valueOf(x);
if (getClass() == PrintStream.class) {
// need to apply String.valueOf again since first invocation
// might return null
writeln(String.valueOf(s));
} else {
synchronized (this) {
print(s);
newLine();
}
}
}
Continúe mirando la función valueOf y vea su código fuente de la siguiente manera:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
Continúe observando la función toString:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Continuar para ver hashCode:
@IntrinsicCandidate
public native int hashCode();
En este momento, se encuentra que este método es un método local, y el desarrollador de JVM proporciona la implementación específica , y el método local no se discutirá por el momento. Hasta ahora, está más o menos claro por qué este ejemplo genera una cadena de cadenas extrañas.
03 Modificar el ejemplo
A continuación, modifique ligeramente el ejemplo anterior y el resultado en este momento es completamente diferente:
public class ExplorationJDKSource {
public static void main(String[] args) {
System.out.println(new A());
}
}
//类A没有定义任何的成员
class A {
@Override
public String toString() {
return "This is class A";
}
}
En este punto, la ejecución generará "Esta es la clase A". ¿por qué? Porque en el código modificado, reescribimos el método toString para devolver la representación de cadena que queremos, en lugar de llamar al método predeterminado.