なぜ==演算子文字列値の比較がJavaにならないのですか?


50

有能なすべてのJavaプログラマーは、==が参照の等価性をチェックするため、==ではなくString.equals()を使用してストリングを比較する必要があることを知っています。

文字列を扱うとき、ほとんどの場合、参照の等価性ではなく値の等価性をチェックしています。==を使用するだけで文字列値を比較できる言語であれば、より直感的になると思います。

比較として、C#の==演算子は文字列 sの値が等しいかどうかをチェックします。参照の等価性を本当に確認する必要がある場合は、String.ReferenceEqualsを使用できます。

もう1つの重要な点は、文字列は不変であるため、この機能を許可しても害はありません。

これがJavaで実装されない特別な理由はありますか?


12
Scalaをご覧ください。Scala==は、オブジェクトの等価性とeq参照の等価性です(ofps.oreilly.com/titles/9780596155957/…)。
ジョルジオ

ただ、ノート、これはあなたを助けることはできませんが、私が覚えている限りでは、あなたが比較できるように文字列リテラルを「==」で
Kgrover

10
@Kgrover:可能ですが、それは参照の等価性と、Javaが文字列マッチングリテラルを積極的に同じオブジェクトへの参照に最適化する方法の便利な副産物です。言い換えれば、それは機能しますが、間違った理由のためです。
tdammers

1
@aviv 演算子は、そのように実装さ==れたEquals場合にのみマッピング==されます。デフォルト動作==と同じであるReferenceEquals(実際には、ReferenceEqualsのオブジェクトバージョンとして定義される==
パトリックHuizinga

3
他の多くのシナリオで多くの意味をなす設計上の決定の結果です。しかし、あなたはそれを知っており、とにかくこれを尋ねているように見えるので、私はあなたの質問に対抗することを余儀なくされています。
ジェイコブライレ

回答:


90

私はそれが単なる一貫性、または「最小限の驚きの原則」だと思います。文字列はオブジェクトであるため、他のオブジェクトと異なる方法で処理された場合は驚くでしょう。

Javaが登場した当時(〜1995年)、String文字列をNULLで終わる配列として表現することに慣れていたほとんどのプログラマーにとって、単に何かのようなものを持っているだけではまったく贅沢でした。Stringの振る舞いは当時のものであり、それは良いことです。動作を後で微妙に変更すると、動作中のプログラムに驚くべき、望ましくない影響を与える可能性があります。

補足として、String.intern()文字列の標準的な(インターン化された)表現を取得するために使用できます==。その後、と比較できます。インターンには時間がかかりますが、その後、比較は非常に高速になります。

追加:一部の回答が示唆するものとは異なり、それは演算子のオーバーロードをサポートすることではありません+演算子(連結)は、上で動作StringするJavaは、演算子オーバーロードをサポートしていないにもかかわらず、S。コンパイラで特別なケースとして処理され、に解決されStringBuilder.append()ます。同様に、==特別なケースとして処理された可能性があります。

それでは、なぜ特別な場合に驚く+のに、ではないの==でしょうか?そのため、+単純にはありませんコンパイル非に適用された場合String、オブジェクトそれはすぐには明白ですので。の異なる動作は、==それほど明白ではなく、したがって、それがあなたに当たったとき、はるかに驚くでしょう。


8
特別な場合は驚きを追加します。
Blrfl

17
ストリングスは1995年に贅沢でしたか?本当に??コンピューター言語の歴史を見てください。当時、ある種の文字列を持っていた言語の数は、持っていなかった言語の数をはるかに上回ります。Cとその子孫のほかにヌル終端配列を使用した言語はいくつありますか?
WarrenT

14
@WarrenT:もちろん、いくつかの(ほとんどではないにしても)言語には何らかのタイプの文字列がありましたが、Unicode対応のガベージコレクションされた文字列は1995年には目新しいものだったと思います。たとえば、Pythonは、2000年のバージョン2.0でUnicode文字列を導入しました。そのとき、不変性を選択することも議論の余地がありました。
ジョナスプラッカ

3
@JoonasPulakkaその後、多分あなたはそれを言うために答えを編集する必要があります。現状では、答えの「完全な贅沢」の部分はかなり間違っているからです。
svick

1
インターンにはコストがかかります。割り当て解除されることのない文字列を取得できます。(まあ、捨てることができる独自のインターンエンジンを使用しない限り。)
ドナルフェローズ

32

Javaの作成者であるジェームズゴスリングは、2000年7月にこのように説明しました。

C ++で多くの人が乱用しているのを見たので、私はかなり個人的な選択として演算子のオーバーロードを省きました。過去5年から6年、オペレーターの過負荷について人々を調査するのに多くの時間を費やしてきました。コミュニティは3つの部分に分かれているので、本当に魅力的です。悪魔の出現; 誰かがリストの挿入に+のように使用しているので、演算子のオーバーロードで何かをやったことがあります。その問題の多くは、合理的にオーバーロードできる演算子が約6個しかないにもかかわらず、人々が定義したい演算子が数千から数百万あるという事実に起因しています。


50
ああ、はい、古い「尖った道具を鈍らせて、オーフが自分自身を傷つけないようにする」言い訳。
Blrfl

22
@Blrfl:ツールが解決するよりも多くの問題を作成する場合、それは良いツールではありません。もちろん、これがオペレーターのオーバーロードのケースであるかどうかを判断することは、非常に長い議論に変わる可能性があります。
ジョルジオ

15
-1。これは質問にまったく答えません。Javaに演算子のオーバーロードがあります。==オペレータは、オブジェクトとプリミティブのために過負荷になっています。+演算子はオーバーロードされbyteshortintlongfloatdoubleString私は忘れてしまった他人の、おそらくカップル。これは、過負荷に完全に可能であったであろう==ためStringにも。
ヨルグW

10
@Jorg-いいえ。オペレーターのオーバーロードは、ユーザーレベルで定義することはできません。コンパイラには確かにいくつかの特別なケースがありますが、それはほとんど適格ではありません
-AZ01

9
@Blrfl:オーフが自分自身を傷つけても構いません。私がイライラするのは、彼らが偶然私の目を突き出したときです。
ジョナス

9

言語内の一貫性。異なる動作をする演算子を持つことは、プログラマーにとって驚くべきことです。Javaでは、ユーザーが演算子をオーバーロードすることは許可されていません==。そのため、オブジェクト間の意味は、参照の等価性のみです。

Java内:

  • 数値型間で、==数値の等価性を比較します
  • ブール型間で、==ブールの等価性を比較します
  • オブジェクト間で、==参照IDを比較します
    • .equals(Object o)値を比較するために使用

それでおしまい。シンプルなルールで、欲しいものを簡単に特定できます。これはすべてJLSのセクション15.21で説明されています。これは、理解、実装、および理由付けが簡単な3つのサブセクションで構成されています。

オーバーロード==許可すると、正確な動作はJLSを見て特定のアイテムに指を当てて「それがどのように機能するか」と言うことができるものではないため、コードの推論が難しくなる可能性があります。の正確な動作は==、ユーザーにとって驚くかもしれません。それを見るたびに、戻ってそれが実際に何を意味するかを確認する必要があります。

Javaは演算子のオーバーロードを許可しないため、ベース定義をオーバーライドできる値の等価性テストを行う方法が必要です。したがって、これらの設計の選択によって義務付けられました。 ==Javaでは、数値型の数値、ブール型のブール等価性、およびその他すべての参照等価性をテストします(.equals(Object o)値の等価性に必要な処理をオーバーライドできます)。

これは「この設計決定の特定の結果のユースケースがある」という問題ではなく、「これはこれらの他のことを促進する設計決定であり、これが結果である」という問題です。

文字列interningは、このような例の1つです。JLS 3.10.5によると、すべての文字列リテラルはインターンされます。他の文字列は、呼び出されるとインターンさ.intern()れます。これ"foo" == "foo"は、文字列リテラルによって占有されるメモリフットプリントを最小化するために行われた設計上の決定の結果です。それを超えて、文字列のインターンはJVMレベルであり、ユーザーに少し公開されますが、圧倒的多数の場合、プログラマーに関係するものではありません(プログラマーのユースケースはそうではありませんでした)この機能を検討する際にデザイナーにとってリストの上位にあったもの)。

人々がいることを指摘する+と、+=文字列のために、オーバーロードされます。しかし、それはここにもそこにもありません。これは、場合した場合のまま==文字列(とのみ文字列)のために意味する値の平等を持ち、1が必要となる異なる参照の等価のための方法を(唯一の文字列に存在していること)。さらに、これはObjectを取り、ある方法で動作し、ユーザーがStringのすべてメソッドを特別な場合に必要とする別の==方法で動作することを期待するメソッドを不必要に複雑にします。.equals()

==オブジェクトの一貫性のある契約は、参照の等価性のみであり、値の等価性テストする必要がある.equals(Object o)すべてのオブジェクトに存在するということです。これを複雑にすると、非常に多くのことが複雑になります。


答えてくれてありがとう。これは、私がリンクした他の質問に対する素晴らしい答えです。残念ながら、これはこの質問には適していません。コメントに基づいた説明でOPを更新します。言語ユーザーが文字列を比較するときに偽陰性を持ちたいと思うユースケースをもっと探しています。この言語は一貫性としてこの機能を提供します。さらに一歩進んでほしいと思います。おそらく、新しい言語のデザイナーからこのことを考えているのでしょうか?(残念ながら、lang-design.SEはありません)
Anonsage

3
@Anonsageは偽陰性ではありません。それらは同じオブジェクトではありません。それが言っているすべてです。また、Java 8ではnew String("foo") == new String("foo")trueになる可能性があることを指摘する必要があります(ストリング重複排除を参照)。

1
言語設計に関しては、CS.SEは、それがそこで話題になっている可能性があることを宣伝しています。

ああ、ありがとう!将来の言語設計の質問をそこに投稿します。:)そして、はい、残念ながら「偽陰性」は私の質問と私が探しているものを説明する最も正確な方法ではありません。言おうとしています。
-Anonsage

2
「言語内の一貫性」もジェネリック医薬品に役立ちます
ブレンダン

2

Javaは演算子のオーバーロードをサポートしていません==。つまり、プリミティブ型または参照にのみ適用されます。それ以外の場合は、メソッドの呼び出しが必要です。デザイナーがこれをした理由は、彼らだけが答えることができる質問です。推測しなければならないのは、おそらく演算子のオーバーロードが、追加することに興味がなかった複雑さをもたらすためです。

私はC#の専門家ではありませんが、その言語の設計者は、すべてのプリミティブがオブジェクトでstructあり、すべてstructがオブジェクトであるように設定しているようです。C#では演算子のオーバーロードが許可されているため、この配置により、任意のクラスだけStringでなく、任意の演算子で「期待される」方法で動作させることが非常に簡単になります。C ++でも同じことが可能です。


1
「Javaは手段は==だけプリミティブ型または参照に適用される支援演算子のオーバーロードを、ない何か他のものは、メソッドの呼び出しを必要とします。。」:一つは、場合ことを追加することができ==、文字列の平等を意味し、我々は参照の等価のための別の表記が必要になります。
ジョルジオ

@Giorgio:そのとおりです。Gilad Naamanの答えに対する私のコメントをご覧ください。
Blrfl

ただし、2つのオブジェクト(または演算子)の参照を比較する静的メソッドによって解決できます。たとえば、C#のように。
ギラッドナアマン

@GiladNaaman:これは、Javaの現在とは逆の問題を引き起こすため、ゼロサムゲームになります。演算子に平等があり、参照を比較するためにメソッドを呼び出す必要があります。さらに、すべてのクラスがにバインドできるものを実装するという要件を課す必要があります==。これは、演算子のオーバーロードを効果的に追加するものであり、Javaの実装方法に大きな影響を与えます。
Blrfl

1
@Blrfl:そうでもない。参照を比較する定義された方法(ClassName.ReferenceEquals(a,b))と、両方を指すデフォルトの==演算子とEqualsメソッドが常に存在しReferenceEqualsます。
ギラッドナアマン

2

これは他の言語では異なります。

Object Pascal(Delphi / Free Pascal)およびC#では、文字列を操作するときに参照ではなく値を比較するために、等価演算子が定義されています。

特にPascalでは、文字列はプリミティブ型であり(初期化されていない文字列のためにNullreferenceExceptionを取得するPascalについて本当に好きなことの1つであり、単に刺激的です)、コピーオンライトセマンティクスがあるため、(ほとんどの場合)文字列操作が非常に安い(つまり、マルチメガバイト文字列の連結を開始した場合にのみ顕著です)。

したがって、これはJavaの言語設計上の決定です。彼らが言語を設計したとき、彼らはC ++の方法(Std :: Stringなど)に従って、文字列がオブジェクトであるようにしました。

そのため、理由は簡単に推測でき、演算子をコーディングしないことでコンパイラの文字列への例外が発生します。


これは質問にどう答えますか?
-gnat

最後の文(編集の適切な段落で区切った)を参照してください。
ファブリシオ

1
私見は、StringJavaのプリミティブ型である必要があります。他の型とは異なり、コンパイラはString; について知る必要があります。さらに、その操作は非常に一般的であるため、多くの種類のアプリケーションでパフォーマンスのボトルネックを引き起こす可能性があります(ネイティブサポートによって緩和できます)。典型的なstring[小文字]では、そのコンテンツを保持するためにヒープにオブジェクトが割り当てられますが、そのオブジェクトへの「通常の」参照はどこにも存在しません。したがって、単一の間接的なChar[]ものByte[]でもChar[]、別のオブジェクトを介した間接的なものでなくてもかまいません。
supercat 14

1

Javaでは、演算子のオーバーロードは一切ありません。そのため、比較演算子はプリミティブ型に対してのみオーバーロードされます。

「String」クラスはプリミティブではないため、「==」のオーバーロードはなく、コンピューターのメモリ内のオブジェクトのアドレスを比較するデフォルトを使用します。

よくわかりませんが、Java 7または8では、oracleはコンパイラで例外を認識してstr1 == str2str1.equals(str2)


「わかりませんが、Java 7または8では、oracleはstr1 == str2をstr1.equals(str2)として認識するためにコンパイラで例外を作成したと思います。」サンよりもミニマリズムがありました。
ジョルジオ

2
trueの場合、これは非常にいハックです。これは、言語が他のすべてとは異なる扱いをし、参照を比較するコードを壊す1つのクラスが存在することを意味するためです。:-@
Blrfl

1
@WillihamTotland:反対のケースを考えてください。私は2つの文字列を作成し、あれば現在、s1そしてs2、それらに同じ内容を与え、彼らは平等(合格s1.equals(s2))の比較ではなく、同じ参照(==)比較なぜなら、彼らしている2つの異なるオブジェクトを。のセマンティクスを変更して==平等を意味するとs1 == s2、評価trueに使用した場所を評価しfalseます。
Blrfl

2
@Brlfl:それは事実ですが、文字列は不変で、インターナブルなオブジェクトであるため、そもそも依存することは非常に悪いことのように聞こえます。
ウィリハムトットランド

2
@Giorgio:「Javaの7または8 Oracleがstr1.equals(STR2)としてSTR1 == str2のを認識するために、コンパイラで例外を作ったが、」いやは、Java言語仕様は言う:リファレンス平等演算子「等価演算子のオペランドがある場合参照型またはnull型の両方の場合、操作はオブジェクトの等価です。」それはすべての人々です。Java 8の初期ドラフトでも、この点に関して新しいことは何も見つかりませんでした。
デビッドトンホーファー14年

0

Javaは、==一方のオペランドを他方のオペランドに変換できる場合はいつでも演算子が有効であり、そのような変換の結果を未変換のオペランドと比較する必要があるという基本的な規則を守るように設計されているようです。

このルールはJavaに固有のものではありませんが、言語の他のタイプに関連する側面の設計に、広範囲に及ぶ(そして不幸なことに)いくつかの影響があります。それはの動作を指定することであっクリーナーだろう==オペランドの型の特定の組み合わせに関して、およびタイプXとYの組み合わせ禁じるx1==y1x2==y1意味するものではありませんがx1==x2、しかし、言語はほとんどそれを行うませんが、[その理念の下で、double1 == long1いずれかのかどうかを示す必要がありますdouble1正確な表現ではないlong1か、コンパイルを拒否します。int1==Integer1禁止する必要がありますが、オブジェクトが特定の値を持つボックス化された整数であるかどうかをテストする便利で効率的な非スロー手段が必要です(ボックス化された整数ではないものとの比較は単にを返す必要がありますfalse)。

==演算子を文字列に適用することに関して、Javaが型Stringとのオペランド間の直接比較を禁止している場合Object、の動作における驚きをかなり回避==できたかもしれませんが、驚くようなことではないそのような比較のために実装できる動作はありません。型で保持される2つの文字列参照が型で保持さObjectれる参照と異なる動作をするStringことは、それらの動作のいずれかが合法的な型の比較と異なることよりも驚くべきことではありません。String1==Object1が合法である場合、それは、の振る舞いString1==String2Object1==Object2一致String1==Object1する唯一の方法は、それらが互いに一致することであることを意味します。


私は何かを見逃しているに違いありませんが==、オブジェクトのIMHO は単に(null-safe)equalsを呼び出し、他の何か(たとえば、===またはSystem.identityEqual)をID比較に使用する必要があります。プリミティブとオブジェクトの混合は、最初は禁止され(1.5より前にオートボクシングはありませんでした)、次にいくつかの簡単なルールが見つかりました(例えば、null-safe unbox、それからキャスト、そして比較)。
maaartinus 14年

@maaartinus:優れた言語設計では、値と参照の等価性に別々の等価演算子を使用する必要があります。概念的には、nullの場合にint==Integer演算子を返して値を比較することが可能であったことに同意しますが、そのアプローチは、両方のオペランドを前に同じタイプに無条件に強制する他のすべての状況の動作とは異なりますそれらを比較します。オートアンボクシングができるように努めて場所に置かれた場合、個人的に私は疑問に思う...無意味ではなかった振る舞いを持っているfalseInteger==int==Integer
supercat

...オートボックスintを作成して参照比較を行うのはばかげている[しかし、常に失敗するとは限りません]。そうでない場合、NPEで失敗する可能性のある暗黙的な変換を許可する理由はありません。
supercat

私の考えは一貫していると思います。より良い世界で==は、とは関係ないことに注意してくださいidentityEquals。+++「値と参照の等価性のための別個の等価演算子」-しかし、どれですか?私は両方のプリミティブ検討したい==equalsやって価値の意味での比較equalsを見ての参照のを。+++すると==意味equals、そしてint==Integerオートボクシングやるとヌル・安全なequalsを使って参照を比較する必要があります。+++怖いのですが、私のアイデアは本当に私のものではなく、Kotlinがやっていることです。
maaartinus

@maaartinus:==参照の等価性を一度もテストしていない場合、nullセーフな値の等価性テストを賢明に実行できます。それは事実ないテスト基準の平等は、しかし、厳しくそれは矛盾せずに混在参照/値の比較を扱うことができる方法を制限します。また、Javaは、関係する型の組み合わせに基づいて特別な動作を生成するのではなく、演算子が両方のオペランドを同じ型に昇格させるという概念に固定されていることに注意してください。例えば、16777217==16777216.0f戻りtrueそれは第1オペランドの非可逆変換を行うためfloatながら...、
supercat

0

一般に、2つのオブジェクト参照が同じオブジェクトを指しているかどうかをテストできるようにするには、非常に正当な理由があります。私が書いたことがたくさんありました

Address oldAddress;
Address newAddress;
... populate values ...
if (oldAddress==newAddress)
... etc ...

そのような場合、私はequals関数を持つ場合と持たない場合があります。そうすると、equals関数は両方のオブジェクトの内容全体を比較する可能性があります。多くの場合、単にいくつかの識別子を比較します。「AとBは同じオブジェクトへの参照」であり、「AとBは同じコンテンツを持つ2つの異なるオブジェクト」は、もちろん2つのまったく異なるアイデアです。

おそらく、文字列のような不変オブジェクトの場合、これはそれほど問題ではありません。不変オブジェクトでは、オブジェクトと値は同じものと考える傾向があります。まあ、「私たち」と言うとき、少なくとも「私」を意味します。

Integer three=new Integer(3);
Integer triangle=new Integer(3);
if (three==triangle) ...

もちろんそれはfalseを返しますが、私は誰かがそれが本当だと思っているのを見ることができます。

ただし、==がオブジェクト一般のコンテンツではなく参照ハンドルを比較すると言うと、文字列の特別なケースを作成すると混乱する可能性があります。ここで他の誰かが言ったように、2つのStringオブジェクトのハンドルを比較したい場合はどうでしょうか?文字列に対してのみ行う特別な機能はありますか?

そして何について ...

Object x=new String("foo");
Object y=new String("foo");
if (x==y) ...

それらは2つの異なるオブジェクトであるために偽ですか、それとも内容が等しい文字列であるために真ですか?

そう、私はプログラマーがこれに戸惑うことを理解しています。私はそれを自分でやりました。つまり、myString.equals( "foo")の場合はmyString == "foo"の場合に書き込みます。しかし、すべてのオブジェクトの==演算子の意味を再設計する以外には、その対処方法がわかりません。


Scalaなど、JVM上の他の現代言語==は「等しい文字列」を意味することに注意してください。
アンドレスF.

@AndresF。(shrug)Javaでは、「<」は「より小さい」を意味しますが、XMLでは「タグを開く」ことを意味します。VBでは、「=」は「等しい」を意味しますが、Javaでは割り当てにのみ使用されます。異なる言語が異なる記号を使用して同じことを意味すること、または同じ記号を異なることを意味することは驚くことではありません。
ジェイ

それはあなたの答えを掘り下げることではありませんでした。ただのコメントでした。==最後に述べたように、Javaよりも新しい言語がの意味を再設計する機会をとったことを指摘していました。
アンドレスF.

@AndresF。そして、私の返事はあなたのコメントを掘り下げることではなく、異なる言語がこれらの問題に異なる方法でアプローチしていると言っているだけです。:-)私は実際にVBがこれを処理する方法が好きです...ヒスとVB hatersからのブーイングのために一時停止します... 「Is」は、2つのオブジェクトのハンドルを比較します。それは私にとってより直感的なようです。
ジェイ

承知しました。ただし、ScalaはVisual BasicよりもJavaに近い方法です。Scalaの設計者は、Javaの使用==がエラーを起こしやすいことに気づいたと思います。
アンドレスF.

0

これは、のために有効な質問でStrings、文字列のため、だけでなく、いくつかの「値」を表す他の不変オブジェクト、例えばのためだけではなくDoubleBigIntegerとさえInetAddress

==演算子を文字列やその他の値クラスで使用できるようにするために、3つの選択肢があります。

  • これらすべての値クラスとその内容を比較する方法についてコンパイラーに知らせてください。java.langパッケージのほんの一握りのクラスであれば、それを検討しますが、それはInetAddressのようなケースをカバーしていません。

  • クラスが==比較動作を定義するように、演算子のオーバーロードを許可します。

  • パブリックコンストラクターを削除し、プールからインスタンスを返す静的メソッドを使用して、常に同じ値に対して同じインスタンスを返します。メモリリークを回避するには、Java 1.0には存在しなかったプール内のSoftReferencesのようなものが必要です。そして今、互換性を維持するために、String()コンストラクターを削除することはできません。

今日できることは、演算子のオーバーロードを導入することだけです。個人的には、Javaがそのような経路に進むのは嫌です。

私にとって、コードの読みやすさが最も重要であり、Javaプログラマーは、演算子が言語仕様で定義された固定の意味を持っていることを知っていますが、メソッドはいくつかのコードで定義されており、その意味はメソッドのJavadocで調べる必要があります。文字列比較で==演算子を使用できないことを意味する場合でも、その区別を維持したいと思います。

Javaの比較には、私にとって厄介な点が1つしかありません。それは、自動ボックス化と-アンボックス化の効果です。プリミティブ型とラッパー型の区別を隠します。しかし、それらをと比較すると==、それらは非常に異なっています。

    int i=123456;
    Integer j=123456;
    Integer k=123456;
    System.out.println(i==j);  // true or false? Do you know without reading the specs?
    System.out.println(j==k);  // true or false? Do you know without reading the specs?
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.