Long.valueOf(0).equals(Integer.valueOf(0))falseなのはなぜですか?
質問
この質問は、 strange HashMap.put()の振る舞い
Map<K,V>.put
がK
をとるのにMap<K,V>.get
がObject
をとる理由を理解していると思います。
今、非常にエラーが発生しやすいシナリオに入ります:
java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five")
m.contains(5); // no complains from compiler, but returns false
Long
の値がint
の範囲内で値が等しい場合にtrueを返すことでこれを解決できませんでしたか?
解決
Long.javaのソース
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
つまり等しくするには、Long型である必要があります。の主な違いは次のとおりです:
long l = 42L
int i = 42;
l == i
上記の例では、プリミティブではint値の暗黙的な拡張が発生する可能性がありますが、オブジェクト型では、暗黙的にIntegerからLongに変換する規則はありません。
Java Puzzlers もご覧ください。これに似た例がたくさんあります。
他のヒント
一般的に言えば、 equals()、オブジェクトは(サブクラスであっても)まったく同じクラスではない別のオブジェクトと等しいと見なすべきではありません。対称性を考慮してください-a.equals(b)がtrueの場合、b.equals(a)もtrueでなければなりません。
2つのオブジェクト、foo
のクラスSuper
、およびbar
のクラスSub
があり、これはequals()
を拡張します。ここで、特にfoo.equals(bar)
として呼び出される場合の、SuperでのObject
の実装を検討します。 Fooは、バーがHashSet<Sub>
として厳密に入力されていることのみを知っているため、正確な比較を行うには、Superのインスタンスであるかどうかを確認し、そうでない場合はfalseを返します。それで、この部分は大丈夫です。現在では、すべてのインスタンスフィールドなど(または実際の比較実装が何であれ)を比較し、それらが等しいことがわかります。これまでのところ、とても良い。
ただし、コントラクトによって、bar.equals(foo)もtrueを返すことがわかっている場合にのみtrueを返すことができます。 barはSuperの任意のサブクラスになり得るため、equals()メソッドがオーバーライドされるかどうかは不明です(おそらくオーバーライドされる場合)。したがって、実装が正しいことを確認するには、対称的に記述し、2つのオブジェクトが同じクラスであることを確認する必要があります。
より基本的には、異なるクラスのオブジェクトを実際に等しいと見なすことはできません。この場合、たとえば、1つだけが<=>に挿入できるためです。
はい。ただし、すべては比較アルゴリズムと変換までの距離にかかっています。たとえば、m.Contains("5")
を試してみるとどうなりますか?または、5を最初の要素として配列に渡すとどうなりますか?簡単に言えば、<!> quot;タイプが異なる場合、キーは異なる<!> quot;。
次に、object
をキーとしてコレクションを取得します。 put
を5L
にした後、5
、"5"
、...を取得しようとするとどうなりますか? 5F
int
とlong
とshort
があり、float
を確認したい場合はどうなりますか?
これは一般的なコレクション(またはテンプレート化されたもの、またはそれを呼び出すもの)であるため、特定の値の種類について特別な比較を確認し、実行する必要があります。 Kがdouble
の場合、渡されたオブジェクトが<=>、<=>、<=>、<=>、...であるかどうかを確認し、変換して比較します。 Kが<=>の場合、渡されたオブジェクトが...
ポイントを取得します。
別の実装では、型が一致しなかった場合に例外をスローすることもできましたが、そうすることを望みます。
あなたの質問は一見理にかなっているように見えますが、2つの異なるタイプに対してtrueを返すのは、契約ではないにしても、equals()の一般的な規則に違反することになります。
Java言語の設計の一部では、C ++とは異なり、Objectsが暗黙的に他の型に変換することはありませんでした。これは、Javaを小さくシンプルな言語にするための一部でした。 C ++の複雑さの合理的な部分は、暗黙的な変換と他の機能との相互作用に起因しています。
また、Javaにはプリミティブとオブジェクトの間にはっきりとした二分法があります。これは、この違いが最適化として隠されている他の言語とは異なります。これは、LongおよびIntegerがlongおよびintのように動作することを期待できないことを意味します。
これらの違いを隠すためにライブラリコードを作成できますが、実際にはプログラミング環境の一貫性を低下させることで害を及ぼす可能性があります。
だからあなたのコードは...でなければなりません...
java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(5L)); // true
javaがコードをオートボクシングしていることを忘れているので、上記のコードは同等です
java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(new Long(5))); // true
System.out.println(m.containsKey(new Long(5L))); // true
問題の一部はオートボクシングです。他の部分は、他のポスターが述べているように、あなたが異なるタイプを持っていることです。
他の回答では、失敗の理由を適切に説明していますが、この問題に関してエラーが発生しにくいコードの記述方法については説明していません。型キャストを追加することを覚えておく必要があります(コンパイラのヘルプはありません)。Lなどのサフィックスプリミティブは受け入れられないだけです。
プリミティブがある場合(および他の多くの場合)、GNU troveコレクションのコレクションを使用することを強くお勧めします。たとえば、物事をプリミティブなlongとして内部的に保存するTLongLongHashMapがあります。その結果、ボクシング/アンボクシングに終わることはなく、予期しない動作に終わることもありません。
TLongLongHashMap map = new TLongLongHashMap();
map.put(1L, 45L);
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler
int x = map.get(1); // Helpful compiler error. x is not a long
int x = (int)map.get(1); // OK. cast reassures compiler that you know
long x = map.get(1); // Better.
など。型を正しくする必要はありません。また、何かおかしい(intにlongを格納しようとする)場合、コンパイラはエラー(修正またはオーバーライド可能)を表示します。
自動キャストのルールは、比較も適切に機能することを意味します:
if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well
おまけとして、メモリのオーバーヘッドと実行時のパフォーマンスが大幅に向上しています。