01 Basic knowledge
Simply put, an immutable class is a class defined with the final keyword, as follows:
final class ClassName{
}
The familiar final keyword is usually used to define constants. Methods and variables declared with final have the following characteristics:
1. Methods declared as final are not allowed to be overridden.
02 A miraculous example
Observe the following example:
public class ExplorationJDKSource {
public static void main(String[] args) {
System.out.println(new A());
}
}
//类A没有定义任何的成员
class A {
}
Running the code yields the following output:
A@4eec7777
Why is there such a situation? The reason for this is that by default, when an object is printed directly as a string , the object's ` method is called toString()`
to get the string representation.
In this example, the class A
does not define any members, so it inherits the default ` toString()`
method implementation. The default ` toString()`
method returns a string consisting of the class name and the hash code of the object . Therefore, the "A" in the output string "A@4eec7777" is the class name, and "4eec7777" is the hash code of the object.
After decompiling the compiled .class file, you will see the following output:
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
}
From the decompiled output, we can see that the code actually calls the ` public void println(Object x) ` method in the JDK. View the source code as follows:
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();
}
}
}
Continue to look at the valueOf function and see its source code as follows:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
Continue to look at the toString function:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Continue to view hashCode:
@IntrinsicCandidate
public native int hashCode();
At this time, it is found that this method is a local method, and the specific implementation is provided by the JVM developer, and the local method will not be discussed for the time being. So far, it is roughly clear why this example outputs a string of strange strings.
03 Modify the example
Next, modify the previous example slightly, and the output at this time is completely different:
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";
}
}
At this point, the run will output "This is class A". why? Because in the modified code, we rewrite the toString method to return the string representation we want, instead of calling the default method.