01 基礎知識
簡単に言えば、不変クラスは、次のように、final キーワードで定義されたクラスです。
final class ClassName{
}
おなじみの Final キーワードは通常、定数を定義するために使用されます。Final で宣言されたメソッドと変数には次の特性があります。
1. Final として宣言されたメソッドはオーバーライドできません。
02 奇跡の一例
次の例を見てください。
public class ExplorationJDKSource {
public static void main(String[] args) {
System.out.println(new A());
}
}
//类A没有定义任何的成员
class A {
}
コードを実行すると、次の出力が得られます。
A@4eec7777
なぜそのような状況が起こるのでしょうか?その理由は、デフォルトでは、オブジェクトがstring として直接出力されるときに、オブジェクトの ` メソッドが呼び出されてtoString()`
文字列表現が取得されるためです。
この例では、クラスはA
メンバーを定義していないため、デフォルトの `toString()`
メソッド実装を継承します。デフォルトの `メソッドは、クラス名とオブジェクトのハッシュ コードで構成される文字列toString()`
を返します。したがって、出力文字列「A@4eec7777」の「A」はクラス名、「4eec7777」はオブジェクトのハッシュコードです。
コンパイルされた .class ファイルを逆コンパイルすると、次の出力が表示されます。
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
}
逆コンパイルされた出力から、コードが実際にJDK の` public void println(Object x) ` メソッドを呼び出していることがわかります。ソース コードは次のように表示されます。
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();
}
}
}
引き続き valueOf 関数を見て、次のようにそのソース コードを確認します。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
引き続き toString 関数を見てみましょう。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
引き続きハッシュコードを表示します。
@IntrinsicCandidate
public native int hashCode();
現時点では、このメソッドはローカル メソッドであることがわかり、具体的な実装はJVM開発者によって提供されるため、当面はローカル メソッドについては説明しません。これまでのところ、この例が奇妙な文字列を出力する理由はほぼ明らかです。
03 例を変更する
次に、前の例を少し変更すると、この時点での出力はまったく異なります。
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";
}
}
この時点で、実行により「This is class A」と出力されます。なぜ?変更されたコードでは、デフォルトのメソッドを呼び出す代わりに、必要な文字列表現を返すように toString メソッドを書き換えているためです。