「x == y」が真なのに「x.equals(y)」が偽になるケースがあるのですがこれはJavaのバグでしょうか?1.5.0で試しています。


Integer x = 1;
System.out.println(x == 1.0); // true
System.out.println(x.equals(1.0)); // false

回答の条件
  • 1人2回まで
  • 登録:2007/01/30 02:19:10
  • 終了:2007/01/30 15:21:36

ベストアンサー

id:flashrod No.3

flashrod回答回数31ベストアンサー獲得回数32007/01/30 10:32:42

ポイント100pt

==では二項数値昇格が起こるのにInteger.equalsの実装はそうなっていない、これはバグじゃないか、という話ですよね。

Integer#equals()の仕様は「実引数が null でなく,このIntegerオブジェクトと同じ int 値を表現するIntegerオブジェクトである場合に,またその場合にだけ,true を返す。」と決まっているので、仕様かどうかといわれれば、仕様です。

で、Autoboxingも導入されて1.0が暗黙にDoubleになっているのに気づきにくい状況でこの仕様はまずいんじゃないか、という事なら、なんとなく同情できますが、今まで偽だったnew Integer(1).equals(new Double(1.0))を真にする、という仕様変更はあり得ないと思います。

そもそも浮動小数点を==で比較しちゃだめ、っていうのは数値計算の基本だし、これはこういうものでいいのではないでしょうか。

id:westfish

なるほど。

つまり「==はオブジェクトの同一性の判定」「equalsは値が同じかどうかの判定」という理解をしていると不自然に感じてはしまうけども、現にそういう仕様だし、仕様が変更されることも考えにくい、ということですね?

2007/01/30 11:00:01

その他の回答(5件)

id:iwaim No.1

iwaim回答回数215ベストアンサー獲得回数192007/01/30 02:55:36

ポイント10pt

Integerクラスのequalsメソッドはオブジェクトの比較を行うからです。「1.0」はIntegerではないのでfalseを返します。

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Integer.html#e...(java.lang.Object)

id:westfish

該当する部分のJavaのソースを読んだのでそれはわかっているのですが「そういう実装で本当にいいのか?」と思った次第です。

2007/01/30 10:06:22
id:imaa No.2

imaa回答回数34ベストアンサー獲得回数02007/01/30 04:20:46

ポイント10pt

違いはIDでの比較と変数の中身の比較です。

http://msugai.fc2web.com/java/equals.html

id:westfish

はい。

しかし、そのリンク先で述べられている通り

「==はオブジェクトの同一性の判定」「equalsは値が同じかどうかの判定」とすると

x == yの時、xとyは同じ物なのだからx.equals(y)が成り立つと考えてしまいがちです。

今回のケースはリテラルの1.0がauto-boxingされることがきっかけで、文字列の比較とはまったく逆の「==がtrueなのにequalsがfalse」という現象が起きています。

これを「従来の教科書のほとんどが間違っている」と主張すべきか「Javaの実装が変」と主張すべきかがよくわかりません。個人的には現状のInteger#equalsの実装がおかしいだけのように思います。

2007/01/30 10:20:17
id:flashrod No.3

flashrod回答回数31ベストアンサー獲得回数32007/01/30 10:32:42ここでベストアンサー

ポイント100pt

==では二項数値昇格が起こるのにInteger.equalsの実装はそうなっていない、これはバグじゃないか、という話ですよね。

Integer#equals()の仕様は「実引数が null でなく,このIntegerオブジェクトと同じ int 値を表現するIntegerオブジェクトである場合に,またその場合にだけ,true を返す。」と決まっているので、仕様かどうかといわれれば、仕様です。

で、Autoboxingも導入されて1.0が暗黙にDoubleになっているのに気づきにくい状況でこの仕様はまずいんじゃないか、という事なら、なんとなく同情できますが、今まで偽だったnew Integer(1).equals(new Double(1.0))を真にする、という仕様変更はあり得ないと思います。

そもそも浮動小数点を==で比較しちゃだめ、っていうのは数値計算の基本だし、これはこういうものでいいのではないでしょうか。

id:westfish

なるほど。

つまり「==はオブジェクトの同一性の判定」「equalsは値が同じかどうかの判定」という理解をしていると不自然に感じてはしまうけども、現にそういう仕様だし、仕様が変更されることも考えにくい、ということですね?

2007/01/30 11:00:01
id:Strada No.4

Strada回答回数18ベストアンサー獲得回数12007/01/30 11:53:25

ポイント20pt

Javaでは過去の経緯(主に実行速度の観点)からプリミティブ型と呼ばれる型が言語仕様として残ってしまいました。

オブジェクト指向言語であるならば、「プリミティブ型と呼ばれる特殊な型は存在せずに、全てIntegerクラスのインスタンスとして管理されるべきである」と、Java否定派の方が好んで持ち出す話です。

実際にC#では完全なオブジェクト指向に近づける為、プリミティブ型を採用せずに全てInt32のようなクラスのインスタンスとして解釈されます。

いわゆるJava5のauto-boxingが働いているわけです。

とは言っても、C#でも実行速度の点では考慮しており、クラスでありプリミティブ型のような扱いを内部で行う為に構造体クラスという特殊なクラスが用意されています。

さて、質問の内容ですが、バグか?という問いにはNOとなります。

どうして挙動が変わるかに関してはauto-boxingやequalsと==の違いを理解しているようなので改めて説明する必要はないでしょう。

flashrod氏も触れているように、言語仕様としては正しいが運用上の問題を内包してしまったと捉えるのが妥当なのではないでしょうか?

経緯と問題を知った上で、現代版Effective Javaを作るのであれば「項番:XX auto-boxingに注意して比較演算子を使う」というようなTipsとなる感じがします。

よって「Javaのバグではなく、質問者のコードがバグである」というのが私の回答です。

id:snaruseyahoo No.5

snaruseyahoo回答回数491ベストアンサー獲得回数42007/01/30 12:21:39

http://www.nextindex.net/java/equals.html

java関数equals()とは、文字列が同じかどうかを判定する関数です。よってInteger x = 1;ですから、1と1.0は数値としては同じなので、System.out.println(x == 1.0); // trueとなりますが、文字列としては異なるので、System.out.println(x.equals(1.0)); // falseとなります。もし、これをtrueにしたいのであれば、equalの代わりに、equalIgnoreCase関数をお試しください。

よろしければ、下記アドレスもご覧ください。

http://www.geocities.jp/snaruse_intage/index.html

id:westfish

その解釈は正しくないと思いますし、

太極拳のページはこの質問には無関係かと思います。

2007/01/30 12:30:45
id:quintia No.6

quintia回答回数562ベストアンサー獲得回数712007/01/30 13:44:54

ポイント20pt

多分もう理解されているのでしょうが、これは == と equalsメソッドの問題というよりも boxing の問題として認識した方がスッキリしますね。


見た目のコードではなくて、

Integer x = new Integer(1);
System.out.println(x.intValue() == 1.0); // true
System.out.println(x.equals(new Float(1.0))); // false

というソースコードをここでは考えるべきなのでしょう。(正確にautoboxing/unboxingの仕様を調べていないのでこれが正解かどうかは保証できません。すみません)

この様にしてみると == と equalsメソッドの関係に対して、バグじゃないのか? という風には見えません。


「autoboxing/unboxing の仕様」と、「== と equalsメソッド」との間に、「ちょっとそれはまずいんじゃないの?」とか「こうやって脳内変換して考えなきゃならないのはよくないのでは?」ということに関しては、確かにこりゃ紛らわしいなぁ、とは思いました。


蛇足

IntegerとFloatが、Numberを共通の親クラスにしていて、かつ intValue()メソッドはNumberで定義されているのに、Integer#equals(Object) が true になるのが Integer クラスを引数にした時だけ、というのも気になりましたが。でもこの辺は人それぞれで考え方は違うでしょうね……。

id:westfish

そうですね、Integer#equalsの中で「obj instanceof Integer」とやっているところを「obj instanceof Number」にすべきではないか?というのが最初の「バグじゃないか?」というセリフだったのですけども、みなさんの話を聞いているうちに「どちらの仕様を選ぶか」という問題だと思えてきました。現状の仕様にも微妙なところはありますが、変更するほどのメリットもあるわけではないので「こういう仕様だ」と納得するしかないようですね。

2007/01/30 15:06:12
  • id:tamtam3
    あまりにも、アホらしいのでコメント欄に
    単純に == と equals() の違いを把握してないだけ


  • id:salic
    仕様です (*´▽`)
  • id:kn1967
    tamtam3氏>あまりにも、アホらしいのでコメント欄に
    tamtam3氏>単純に == と equals() の違いを把握してないだけ

    コメントを見て、他人でも、少し腹が立ったが、、、、
    調べる見ると質問者さんはPerlやPHPに関する回答もしておられるのだから「データ型といような基本的な仕様くらいは読んでいて当然」と捉えられても仕方ないのかな?
    だからといって、自分の保有ポイントを使って質問して何が悪いというのだろう?
  • id:westfish
    salicさん
    もし==とequalsの挙動について定めている仕様書があれば教えていただけると幸いです。英語でも構いません。

    kn1967さん
    フォローありがとうございます。
    こちらの質問も少々言葉足らずだったように思います。
    最初から2番目の回答へのレスのような内容を書いていれば誤解は避けられたのですが…
  • id:westfish
    今まで下のようなものを「APIの使い方のドキュメント」ととらえていたのですが、改めて確認してみるとちゃんと「API 仕様」と書かれていますね。勘違いしていました。これに「結果が true になるのは、引数が null ではなく、このオブジェクトと同じ int 値を含む Integer オブジェクトである場合だけです。」と書かれているので、この挙動は仕様通りと言うことになりますね。
    http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/overview-summary.html
  • id:flashrod
    「==はオブジェクトの同一性の判定」はオブジェクト型の時はそのとおりです。プリミティブ型のときは値の比較です。
    で、話を複雑にしているのはautoboxing/unboxingだと思うんですが
    オブジェクトa==プリミティブbのときはunboxingされて
    プリミティブa==プリミティブbと解釈される。
    オブジェクトa.equals(プリミティブb)はautoboxingされて
    オブジェクトa.equals(オブジェクトb)と解釈される、という言語仕様
    あと数値==で二項数値昇格が起こるということと、Integer#equals()の仕様、
    これらをすべて理解しないと質問の挙動は理解できないってことでしょう。
  • id:westfish
    flashrodさん
    >オブジェクトa==プリミティブbのときはunboxingされて
    >プリミティブa==プリミティブbと解釈される

    この仕様がどこに記載されているのか見つけることができませんでした。
  • id:flashrod
    >>オブジェクトa==プリミティブbのときはunboxingされて
    >この仕様がどこに記載されているのか
    http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.21.1
    に、「一方が数値でもう一方が数値に変換できるときは数値として比較」「二項数値昇格とunboxingが行われる」と書いてあります。
  • id:H30
    本筋とは逸れるけど、Java言語仕様の根幹だけど自分的には以下の方がビミョー感が満載です。equalsの判定が左辺と右辺で交換可能でないなんて。。
    ----------
    String s1 = "sample";
    String s2 = null;
    System.out.println(s1.equals(s2)); // false
    System.out.println(s2.equals(s1)); // NullPointerException
    ----------

    それと、JDK1.5.0については、BigDecimal#toStringの仕様変更の方が致命的な仕様バグの匂いがプンプンするんですけどね。
  • id:westfish
    flashrodさん
    おお、まさにこれですね!
    つまり「x == y」と「x.equals(y)」で、yがプリミティブ型の数値の場合には、前者はxがunboxingされ後者はyがboxingされるということと、プリミティブ型のオペランドに対する「==」は値の同一性の判定で参照型のオペランドに対するそれよりもtrueになる範囲が広いということがうまく重なって、今回のような挙動が起きているというわけですね。なるほど。
  • id:westfish
    H30さん
    等号の左右が交換可能でないと気持ちが悪いですよね。
    この場合はx.equals(y)とy.equals(x)なので、まぁ交換可能でなくても仕方がないかなぁという気もしますが。
    RubyやPythonのようにシングルトンなヌルオブジェクトを用意すればいいだけなのに…まぁ過去のしがらみというやつでしょうか。
  • id:Strada
    >>H30さん
    それは数学的な"同値"の定義ですから、Javaの言語仕様とかの話ではありません。
    数学的な見方をするならば、nullと"sample"は比較できないのです。

    いわゆる反射律、対称律、推移律の対象律の事ですね
    集合Sに属する a,b に2項関係が成り立つとき、a = b ならば b = a
    しかし、nullと"sample"は2項関係にあるとは言えません。
    (大雑把に言えば比較できないとダメ、そもそもnullがString集合に入っているとも言えない)
    よって、nullの場合にequalsの対象律が成立しないことは問題なかったりします。
  • id:H30
    >>westfishさん
    本家(?)のSmalltalkもnilはUndefinedObjectのインスタンスなのにねぇ。

    >>Stradaさん
    nullがString集合になくて比較不能ならば、できれば、両者ともNullPointerExceptionにしてくれればスッキリするのですが。

    とはいえ、nullの扱いの議論は難しいのでこの辺で。
    以下、参考。
    http://www.geocities.jp/mickindex/database/db_3vl.html

この質問への反応(ブックマークコメント)

トラックバック

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません