web-dev-qa-db-ja.com

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

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

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

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

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

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

50
l46kok

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

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

補足として、String.intern()を使用して文字列の正規(インターン)表現を取得できます。その後、==と比較できます。インターンには時間がかかりますが、その後は比較が非常に速くなります。

追加:一部の回答とは異なり、演算子のオーバーロードのサポートについてではありません。 Javaは演算子のオーバーロードをサポートしていませんが、+演算子(連結)はStringsで機能します。これは、コンパイラで特殊なケースとして単に処理され、StringBuilder.append()に解決されます。同様に、==は特殊なケースとして処理された可能性があります。

それなら、なぜ+ではなく、特殊なケース==で驚くのですか?なぜなら、+は、String以外のオブジェクトに適用した場合、単にコンパイルしないので、すぐにわかります。 ==の異なる動作は、あまり目立たなくなり、ヒットしたときの驚きが大きくなります。

90
Joonas Pulakka

James Gosling 、Javaの作成者がこのように説明しました 2000年7月に戻る

C++で乱用する人が多すぎるのを見たので、オペレーターのオーバーロードをかなり個人的な選択として省略しました。私は過去5〜6年間、オペレーターの過負荷について人々を調査してきた多くの時間を費やしてきましたが、コミュニティーが3つの部分に分割されるので、それは本当に魅力的です。おそらく、人口の約20〜30%がオペレーターの過負荷を悪魔の産卵; +のようにリストの挿入に使用していて、本当に混乱を招いているため、誰かが演算子のオーバーロードを使用して何かを実行しました。その問題の多くは、かなり過負荷にできるオペレーターが約6ダースしかないにもかかわらず、ユーザーが定義したいオペレーターが数千または数百万あるため、選択する必要があり、多くの場合、選択肢が多いためです。直感と矛盾します。

32
Ross Patterson

言語内の一貫性。異なる動作をするオペレーターがいることは、プログラマーにとって驚くべきことです。 Javaは、ユーザーが演算子をオーバーロードすることを許可しません-したがって、オブジェクト間の_==_の唯一の妥当な意味は、参照の等価性です。

Java内:

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

それでおしまい。シンプルなルールで、必要なものを簡単に識別できます。これはすべて JLSのセクション15.21 でカバーされています。これは、理解、実装、および理由付けが容易な3つのサブセクションで構成されています。

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

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

これは「この設計決定の特定の結果にユースケースがあるか」という問題ではなく、「これはこれらの他のことを容易にするための設計決定であり、これはその結果です」という問題です。

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

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

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

9
user40980

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

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

2
Blrfl

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

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

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

つまり、Javaの言語設計の決定です。彼らが言語を設計したとき、彼らはC++の方法(Std :: Stringのような)にしたがったので、文字列はオブジェクトです。つまり、文字列をプリミティブ(実際のもの)にするのではなく、Cが実際の文字列型を欠いていることを補うためのハックです。

だから、なぜかという理由で、私は彼らがそれを彼らの側で簡単にすることを推測することができるだけで、演算子をコーディングしないことは文字列に対するコンパイラで例外を作る。

2
Fabricio Araujo

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

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

わかりませんが、Java 7または8では、コンパイラが_str1 == str2_をstr1.equals(str2)として認識するようにコンパイラで例外を作成しました

1
Gilad Naaman

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

この規則はJavaに固有のものではありませんが、言語の他の型に関連する側面の設計に広範囲にわたる(そして残念なことにIMHOに)影響を及ぼします。オペランドタイプの特定の組み合わせに関して==の動作を指定し、タイプXとYの組み合わせを禁止すると、x1==y1x2==y1x1==x2、しかし言語はめったにそうしない[その哲学の下では、double1 == long1double1exactlong1の表現ではないかどうかを示す必要がある、またはそうでなければ、コンパイルを拒否します。 int1==Integer1は禁止する必要がありますが、オブジェクトが特定の値を持つボックス化された整数であるかどうかをテストする便利で効率的な非スロー手段が必要です(ボックス化された整数ではないものと比較すると、単にfalse)]。

==演算子を文字列に適用することに関して、JavaがタイプStringObjectのオペランド間の直接比較を禁止していた場合、 ==の動作の驚きはかなりよく回避されていますが、驚くほどのことではないような比較のために実装できる動作はありません。2つの文字列参照がObjectに保持されていると、保持されている参照とは動作が異なりますタイプStringは、これらの動作のいずれかが正当な混合タイプの比較の動作と異なる場合よりも驚くほど少なくなります。String1==Object1が合法である場合、これは、 String1==String2Object1==Object2が一致するString1==Object1は、それらが互いに一致するためのものです。

0
supercat

一般に、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を返しますが、それがtrueであるはずだと考える誰かを見ることができます。

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

そして何について ...

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

それらは2つの異なるオブジェクトであるためfalseですか、または内容が等しいStringであるためtrueですか?

だからはい、プログラマーがこれに混乱する方法を理解しています。私は自分でやったのですが、もしmyString.equals( "foo")だったらmyString == "foo"だったら書くということです。しかし、すべてのオブジェクトの==演算子の意味を再設計しない限り、それに対処する方法がわかりません。

0
Jay

これはStringsの有効な質問であり、文字列だけでなく、「値」を表す他の不変オブジェクトにも当てはまります。 DoubleBigInteger、さらにはInetAddress

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

  • これらすべての値クラスとその内容を比較する方法についてコンパイラに知らせます。 _Java.lang_パッケージの一部のクラスである場合は、それを検討しますが、InetAddressなどのケースは対象外です。

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

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

今日でもまだ実行できる唯一のことは、オペレーターのオーバーロードを導入することであり、個人的にはJavaがそのルートを進むのが嫌いです。

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

Javaの比較には、私にとって厄介な側面が1つだけあります。それは、自動ボックス化と-unboxingの効果です。プリミティブとラッパータイプの違いを隠します。しかし、それらを_==_と比較すると、まったく異なります。

_    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?
_
0
Ralf Kleberhoff