The HIRO Says

If you smell what The HIRO is cooking!!!

equals() メソッドの実装(2)−symmetry の実現

equals() メソッドの規約の1つに、symmetry があります。
これは、a.equals(b) が true ならば b.equals(a) も true でなければならないというものです。
イメージし易そうでし辛いので、以下に例を示します。

以下のような、何らかのタイトルを意味するクラスがあるとします。

public class Title {

    private String name;

    public Title() {
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


この場合、文字列が等しいことのみを要件と考えてしまい、equals() メソッドを以下のように実装してしまうとどうでしょう。

    public boolean equals(Object o) {

        if (o instanceof Title) {
            // 型が Title ならば、name が等しいか否かを検証する。
            Title target = (Title) o;
            return this.name.equals(target.name);
        }

        if (o instanceof String) {
            // 型が String ならば、name と等しいか否かを検証する。
            String target = (String) o;
            return this.name.equals(target);
        }

        // 上記以外は false!
        return false;
    }


(極端ですが、)このように実装してしまうと、以下のように symmetry を実現できなくなります。

    Title title = new Title();
    title.setName("Java");
    String name = "Java";
        ↓
    title.equals(name) = true;
    name.equals(title) = false;


正しくは、次のように実装することとなります。

    @Override
    public boolean equals(Object o) {

        // reflexivity の検証(パフォーマンス向上策を含む)
        if (o == this) {
            return true;
        }

        // non-nullity と型の検証
        if (!(o instanceof Title)) {
            return false;
        }

        // instanceof で型の検証は済んでいるため、このキャストの動作は保障される。
        Title target = (Title) o;

        // symmetry の検証
        // name が null のケースがあるので、null のケースとそうではないケースとを分ける必要がある。
        return (this.name == null ? target.name == null : this.name.equals(target.name));
    }