5

equalsBloch の素晴らしい本 "Effective Java"は、 が対称でない場合、コレクションの動作は不確定であると指摘していcontainsます。

彼が示した例 (以下に若干の修正を加えて再掲) で、Bloch は「false」が表示されたが、true または Exception も同様に表示された可能性があると述べています。

コレクション内の項目ごとにcontains(Object o)チェックするe.equals(o)かどうかが標準で指定されておらず、前者が実装されている場合は、「true」と表示されます。o.equals(e)ただし、Collections Javadocは、後者でなければならないと明確に述べています (そして、それは私が観察したことです)。

したがって、私が見る唯一の可能性は、「false」またはおそらく例外です (ただし、String Javadocは後者を排除しているようです)。

より広い点は理解しています。非対称性equalsがコレクションの外部のコードで問題を引き起こす可能性がありますが、彼が引用した例ではそれが見られません。

何か不足していますか?

import java.util.List;
import java.util.ArrayList;

class CIString {
  private final String s;

  public CIString(String s) {
    this.s = s;
  }

  @Override public boolean equals( Object o ) {
    System.out.println("Calling CIString.equals from " + this.s );
    if ( o instanceof CIString) 
      return s.equalsIgnoreCase( ( (CIString) o).s);
    if ( o instanceof String) 
      return s.equalsIgnoreCase( (String) o );
    return false;
  }
  // Always override hashCode when you override equals
  // This is an awful hash function (everything collides -> performance is terrible!)
  // but it is semantically sound.  See Item 10 from Effective Java for more details.
  @Override public int hashCode() { return 42; }
}

public class CIS {
  public static void main(String[] args) {
   CIString a = new CIString("Polish");
   String s = "polish";

   List<CIString> list = new ArrayList<CIString>();
   list.add(a);
   System.out.println("list contains s:" + list.contains(s));
 }
}
4

2 に答える 2

3

早朝なので、質問の真意を見逃している可能性があります。このコードは失敗します。

public class CIS 
{
    public static void main(String[] args) 
    {
        CIString a = new CIString("Polish");
        String s = "polish";

        List<String> list = new ArrayList<String>();
        list.add(s);
        System.out.println("list contains a:" + list.contains(a));
    }
}

少なくとも、あなたのコードがそれを見つけて、私のコードが見つけられないのは奇妙です(正気の観点から、それが明らかにあなたのコードがどのように書かれているかではありません:-)

編集:

public class CIS {
  public static void main(String[] args) {
   CIString a = new CIString("Polish");
   String s = "polish";

   List<CIString> list = new ArrayList<CIString>();
   list.add(a);
   System.out.println("list contains s:" + list.contains(s));

   List<String> list2 = new ArrayList<String>();
   list2.add(s);
   System.out.println("list contains a:" + list2.contains(a));
 }
}

これでコードが出力されます:

list contains s:false
Calling CIString.equals from Polish
list contains a:true

これはまだ意味がありません...そして非常に壊れやすいです。2 つのオブジェクトが a.equals(b) のように等しい場合、それらは b.equal(a) のように等しくなければなりませんが、コードではそうではありません。

javadocから:

これは対称的です: null 以外の参照値 x と y の場合、y.equals(x) が true を返す場合に限り、x.equals(y) は true を返す必要があります。

したがって、はい、本の例はコレクション API の Javadoc に反している可能性がありますが、原則は正しいです。奇妙に動作する equals メソッドを作成しないでください。そうしないと、最終的に問題が発生します。

編集2:

テキストの要点は次のとおりです。

Sun の現在の実装では、たまたま false が返されますが、これは単なる実装上の成果物です。別の実装では、同じように簡単に true を返したり、実行時例外をスローしたりできます。イコール コントラクトに違反すると、自分のオブジェクトに直面したときに他のオブジェクトがどのように動作するかわかりません。

ただし、Javadoc に記載されている内容を考えると、実装アーティファクトではなく動作が修正されているように見えます。

javadoc に含まれていない場合、または javadoc が仕様の一部であることが意図されていない場合は、後日変更され、コードが機能しなくなる可能性があります。

于 2011-09-22T14:25:15.593 に答える
1

私が今見ている本のコピー (第 2 版) では、項目番号は 8 で、対称性要件に関するセクション全体がかなり貧弱に提示されています。

あなたが言及した特定の問題は、使用コードが実装に近すぎて、作成者が作成しようとしているポイントが不明瞭になっていることが原因のようです。list.contains(s)つまり、それを通して ArrayList と String を見て、true を返すか、例外をスローするかについてのすべての推論は、私にはまったく意味がありません。

  • 「使用コード」を実装からさらに遠ざけて、それがどのようになるかを理解する必要がありました。

    void test(List<CIString> list, Object s) {
        if (list != null && list.size() > 0) {
            if (list.get(0).equals(s)) { // unsymmetric equality in CIString
                assert !list.contains(s); // "usage code": list.contain(s)
            }
        }
    }
    

list上記は奇妙に見えますが、ArrayList とsString である限り、テストはパスします。

では、文字列の代わりに別のものを使用するとどうなるでしょうか? new CIString("polish")たとえば、 2 番目の引数として渡すとどうなるでしょうか。

最初のcheckを通過したにもかかわらずequals、アサーションは次の行で失敗します。contains はこのオブジェクトに対して true を返すためです。


ブロックが例外に言及している部分についても、同様の理由が当てはまります。今回は2番目のパラメータを文字列のままにしましたが、最初のパラメータはArrayList以外のList実装を想定していました(合法ですよね)。

  • ご覧のとおり、 List 実装は通常、 から ClassCastException をスローできますcontains。必要なのは、まさにそれを行うものを取得して、テストに使用することだけです。頭に浮かぶのは、適切なコンパレータで元のリストをラップした TreeSet に基づいている可能性があります。

    List<CIString> wrapperWithCce(List<CIString> original,
            Comparator<CIString> comparator) {
        final TreeSet<CIString> treeSet = new TreeSet<CIString>(comparator);
        treeSet.addAll(original);
        return new ArrayList<CIString>() {
            { addAll(treeSet); }
            @Override
            public boolean contains(Object o) {
                return treeSet.contains(o); // if o is String, will throw CCE
            }
        };
    }
    

上記のような list と String "polish" を に渡すとどうなりtestますか? list.get(0).equals(s)チェックには合格しますが、 TreeSet.contains()list.contains(s)から ClassCastException をスローします。

これは、最初のcheckを通過しlist.contains(s)たにもかかわらず、Bloch が例外をスローする可能性があると述べたときに念頭に置いていたケースのようです。equals

于 2011-09-23T07:22:40.763 に答える