Java解惑2-13牧畜場

2573 ワード

George Orwellの「牧畜場(Animal Farm)」の読者は、「すべての動物が平等だ」という大佐の宣言を覚えているかもしれない.次のJavaプログラムはこの宣言をテストしようとしています.では、何が印刷されますか?
public class AnimalFarm{
    public static void main(String[] args){
        final String pig = "length: 10";
        final String dog = "length: " + pig.length();
        System.out. println("Animals are equal: "
                            + pig == dog);
    }
}
このプログラムの表面分析は、Animal are equal:trueを印刷すべきであると考えられるかもしれない.結局、pigとdogはfinalのstringタイプ変数であり、文字シーケンス「length:10」に初期化されています.すなわち、pigおよびdogによって参照される文字列は、常に互いに等しい.ただし、==オペレータは、2つのオブジェクト参照が同じオブジェクトに適切に参照されているかどうかをテストします.この例では、同じオブジェクトに参照されているわけではありません.
Stringタイプのコンパイル期間定数はメモリ限定であることを知っているかもしれません.すなわち、任意の2つのStringタイプの定数式は、同じ文字列が明記されている場合、同じオブジェクト参照で表されます.pigとdogを定数式で初期化すると、同じオブジェクトを指しますが、dogは定数式で初期化されません.言語が定数式で許容される操作を制限し、メソッド呼び出しが含まれていない以上、このプログラムはAnimal are equal:falseを印刷すべきではないでしょうか.
うん、実は違う.プログラムを実行すると、falseだけが印刷されており、他に何もありません.Animal are equal:が印刷されていません.どうしてこの文字列の字面定数を印刷しないのですか?結局それを印刷するのが正しいですね.パズル11の謎解きスキームは、加算としても文字列接続としても==オペレータよりも優先度が高い+オペレータを含む.したがってprintlnメソッドのパラメータは、次のように計算されます.
System.out.println(("Animals are equal: " + pig) == dog);

このブール式の値はもちろんfalseで、プログラムの印刷された出力です.
文字列接続オペレータを使用する場合、通常ではないオペランドを常にカッコで囲むという窮地を回避する方法があります.より一般的には、カッコが必要かどうかを判断できない場合は、それらを囲む適切な方法を選択する必要があります.println文で次のように比較部分を囲むと、所望の出力Animals are equal:falseが生成されます.
System.out.println("Animals are equal: " + (pig == dog));

このプログラムにはまだ問題があることを論証することができる.
できれば、あなたのコードは文字列定数のメモリ制限メカニズムに依存するべきではありません.メモリ制限メカニズムは、仮想マシンのメモリ占有量を減らすために設計されているだけで、プログラマが使用できるツールとして設計されているわけではありません.このパズルが示すように、どの式が文字列定数を生成するかは常に明らかではありません.
さらに悪いことに、コードがメモリ制限メカニズムに依存して動作の正確性を実現している場合は、どのドメインとパラメータが必ずメモリ制限であるかをよく理解する必要があります.コンパイラは、メモリ限定文字列と非限定文字列が同じタイプ(String)で表されるため、これらの不変量をチェックすることはできません.メモリに文字列を限定できなかったため、バグは検出しにくい.
オブジェクト参照を比較する場合は、=オペレータではなくequalsメソッドを優先的に使用する必要があります.オブジェクトの値ではなくオブジェクトの識別子を比較する必要がない限り.この教訓を私たちのプログラムに適用することによって、次のprintln文を与えました.これこそが持つべき姿です.この方法でプログラムを訂正するとtrueが印刷されるのは明らかです.
System.out.println("Animals are equal: " + pig.equals(dog));

このパズルは言語設計者にとって2つの教訓がある.
 
  • 文字列接続の優先度は加算と同じではありません.これは、パズル11で述べたように、文字列接続を実行するために+オペレータを再ロードすることに問題があることを意味する.
  • また、Stringのような修正不可能なタイプについては、その参照する等価性比の等価性がより困惑している.==オペレータは、変更できないタイプに適用される場合に値比較を実行する必要があります.これを実現するには、=オペレータをequalsメソッドの簡便な書き方とし、Systemに類似する単独の方法を提供する.identityHashCodeのメソッドは、参照IDの比較を実行します.