C#とJavaが参照の等価性を「==」のデフォルトとして使用するのはなぜですか?


32

私はしばらくの間、JavaとC#(および他の言語)がデフォルトでの等価性を参照する理由を熟考してきました==

私が行うプログラミング(確かにプログラミングの問題のごく一部にすぎません)では、オブジェクトを比較するとき、参照の等価性ではなく、論理的な等価性がほとんど常に必要です。私は、これらの言語の両方が、==論理的な平等性を持ち.ReferenceEquals()、参照の平等性を使用するのではなく、このルートを採用した理由を考えようとしました。

明らかに、参照の等価性を使用することは実装が非常に簡単で、非常に一貫した動作を提供しますが、今日見られるほとんどのプログラミング手法にうまく適合するとは思えません。

論理比較を実装しようとすることの問題について無知に思われたくはありません。また、すべてのクラスで実装する必要があります。また、これらの言語はかなり前に設計されたものであることに気づきましたが、一般的な疑問が残っています。

これにデフォルトを設定することの大きな利点はありますか、それとも単にデフォルトの動作が論理的等価であるべきであり、クラスに論理的等価性が存在しない場合に参照の等価性にデフォルト設定することは妥当と思われますか?


3
変数は参照であるためですか?変数はポインタのように振る舞うので、彼らはそれのように比較することは理にかなっている
ダニエルGratzer

C#は、構造体などの値型に論理的等価性を使用します。しかし、異なる参照型の2つのオブジェクトに対する「デフォルトの論理的等価性」はどうあるべきでしょうか?または、一方がBから継承されたタイプAである2つのオブジェクトの場合 構造体のように常に「false」ですか?同じオブジェクトが2回参照された場合でも、最初はAとして、次にBとして参照されますか?私にはあまり意味がありません。
Doc Brown

3
言い換えると、C#でなぜオーバーライドするのかを尋ねEquals()ています==か?
svick

回答:


29

Javaが行ったため、C#はそれを行います。Javaは演算子のオーバーロードをサポートしていないため、Javaをサポートしました。値の等価性はクラスごとに再定義する必要があるため、演算子にすることはできませんが、代わりにメソッドにする必要がありました。IMOこれは悪い決断でした。読み書きa == bよりもはるかに簡単a.equals(b)であり、CまたはC ++の経験がa == bあるプログラマにとってははるかに自然ですが、ほとんどの場合間違っています。必要な==場所の使用に起因するバグが、.equals数千のプログラマー時間を無駄にしています。


7
オペレーターの過負荷の支持者は中傷者と同数だと思うので、絶対的な声明として「悪い決断だった」とは言いません。例:私が働いているC ++プロジェクトでは==、多くのクラスをオーバーロードしており、数か月前に、==実際に何を行っているのか分からない開発者がいることを発見しました。何らかの構造のセマンティクスが明らかでない場合、常にこのリスクがあります。このequals()表記は、私がカスタムメソッドを使用していることと、それをどこかで調べる必要があることを示しています。結論:演算子のオーバーロードは一般に未解決の問題だと思います。
ジョルジオ

9
Javaにはユーザー定義の演算子のオーバーロードがないと思います。Javaでは、多くの演算子に二重の(オーバーロードされた)意味があります。見+(数値の)追加と同時に文字列の連結を行う例、について。
ヨアヒムザウアー

14
a == bCはユーザー定義の演算子のオーバーロードをサポートしていないため、Cの経験があるプログラマにとってはどうすればより自然になりますか?(たとえば、文字列を比較するCの方法はstrcmp(a, b) == 0、ではありませんa == b。)
svick

これは基本的に私が考えていたことですが、より多くの経験を積んだ人たちに、明らかな何かを見逃していないことを確認するように頼んだと思いました。
ジッパー

4
@svick:Cには、文字列型も参照型もありません。文字列操作はを介して行われchar *ます。2つのポインタが等しいかどうかを比較することは、文字列を比較することと同じではないことは、私には明らかです。
ケビンクライン

15

短い答え:一貫性

しかし、あなたの質問に適切に答えるために、私たちは一歩後退して、プログラミング言語で平等が意味することの問題に目を向けることをお勧めします。少なくとも3つの異なる可能性があり、さまざまな言語で使用されています。

  • 参照の等価性:aとbが同じオブジェクトを参照する場合、a = bが真であることを意味します。aとbのすべての属性が同じであっても、aとbが異なるオブジェクトを参照している場合は当てはまりません。
  • 浅い等価性:aとbが参照するオブジェクトのすべての属性が同一である場合、a = bが真であることを意味します。浅い等価性は、2つのオブジェクトを表すメモリ空間のビットごとの比較によって簡単に実装できます。参照の平等は浅い平等を意味することに注意してください
  • 深等性:aとbの各属性が同一または完全に等しい場合、a = bが真であることを意味します。深い等価は、参照等価と浅い等価の両方によって暗示されることに注意してください。この意味で、深い平等は平等の最も弱い形であり、参照平等は最も強いです。

これら3つのタイプの等式は、実装が便利であるためによく使用されます。3つの等式チェックはすべて、コンパイラーによって簡単に生成できます(深い等式の場合、コンパイラーは、タグビットを使用して無限ループを防ぐ必要がある場合があります比較されるのは循環参照です)。しかし、別の問題があります。これらはどれも適切ではないかもしれません。

自明でないシステムでは、オブジェクトの平等はしばしば深い平等と参照平等の間の何かとして定義されます。特定のコンテキストで2つのオブジェクトを同等と見なすかどうかを確認するには、一部の属性をメモリ内の位置で比較し、他の属性を完全に異なるものとすることができますが、一部の属性はまったく異なるものにすることができます。私たちが本当に望んでいるのは、「フォースタイプの平等」であり、文学のセマンティック平等でしばしば呼ばれる、本当に素晴らしいものです。私たちのドメインでは、物事が等しければ等しいです。=)

それで、あなたの質問に戻ることができます:

これをデフォルトにすることでいくつかの大きな利点がありますか、それとも単にデフォルトの動作が論理的等価であるべきであり、クラスに論理的等価が存在しない場合はデフォルトを参照等価に戻すのが妥当と思われますか?

「a == b」を任意の言語で書くとき、どういう意味ですか?理想的には、常に同じである必要があります:意味的平等。しかし、それは不可能です。

主な考慮事項の1つは、少なくとも数値のような単純な型の場合、同じ値を代入した後は2つの変数が等しいと予想されることです。下記参照:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

この場合、両方のステートメントで「aはbに等しい」と予想されます。他のものはすべて異常です。ほとんどの言語(すべてではないにしても)は、この規則に従います。したがって、単純型(値とも呼ばれます)を使用すると、セマンティックな等価性を実現する方法がわかります。オブジェクトの場合、それはまったく異なるものになる可能性があります。下記参照:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

最初の「if」は常に真であると期待しています。しかし、2番目の「if」では何を期待しますか?それは本当に依存しています。'DoSomething'は、aとbの(セマンティック)平等を変更できますか?

セマンティックな等価性の問題は、コンパイラがオブジェクトに対して自動的に生成できないこと、および割り当てから明らかであることです。ユーザーが意味的同等性を定義するためのメカニズムを提供する必要があります。オブジェクト指向言語では、そのメカニズムは継承メソッドequalsです。OOコードの一部を読んで、メソッドがすべてのクラスでまったく同じ実装を持つことは期待できません。継承とオーバーロードに慣れています。

ただし、演​​算子では、同じ動作が期待されます。「a == b」と表示された場合、すべての状況で同じタイプの同等性(上記4)が期待されます。そのため、一貫性を目指し、言語設計者はすべての型に対して参照の平等を使用していました。プログラマがメソッドをオーバーライドしたかどうかに依存すべきではありません。

PS:言語DeeはJavaおよびC#とわずかに異なります:等号演算子は、単純型の浅い等価性とユーザー定義クラスのセマンティックな等価性を意味します(ユーザーにある=操作を実装する責任がある-デフォルトは提供されません)。単純型の場合、浅い等式は常に意味的等式であるため、言語は一貫しています。ただし、代価は、等号演算子がデフォルトでユーザー定義型に対して未定義であるということです。実装する必要があります。そして、時々、それはただ退屈です。


2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Javaの言語設計者は、オブジェクトの参照平等とプリミティブの意味平等を使用していました。これが正しい決定であったか、またはこの決定が==オブジェクトのセマンティックな等価性のためにオーバーロードされることを許可するよりも「一貫性がある」ことは私には明らかではありません。
チャールズサルビア

プリミティブにも「参照の等価性と同等」を使用しました。「int i = 3」を使用する場合、番号へのポインターがないため、参照を使用できません。文字列、「ソート」プリミティブタイプでは、より明白です。==(参照等価)を使用するには、「。intern()」または直接割り当て(String s = "abc")を使用する必要があります。
Hbas

1
PS:一方、C#は文字列と一致していませんでした。そして、私見、この場合、それははるかに良いです。
Hbas

@CharlesSalvia:Javaでは、abが同じ型である場合、式は同じものを保持するa==bかどうかaをテストしbます。一方がオブジェクト#291への参照を保持し、もう一方がオブジェクト#572への参照を保持している場合、それらは同じものを保持しません。オブジェクト#291と#572 の内容は同等かもしれませんが、変数自体は異なるものを保持しています。
supercat

2
@CharlesSalviaそれはあなたがa == bそれが何をするのかを見て知ることができるように設計されています。同様に、Aがa.equals(b)オーバーロードすることを確認できますequalsa == b呼び出しの場合a.equals(b)(実装されている場合)、参照による比較ですか、それともコンテンツによる比較ですか?覚えていない?クラスAをチェックする必要があります。何が呼び出されているかわからない場合、コードはすぐに読むことができなくなります。同じシグニチャを持つメソッドが許可されたかのようになり、呼び出されるメソッドは現在のスコープに依存します。このようなプログラムは読むことができません。
ニール

0

私はこれらの言語の両方がそれを反転させて==を論理的に等しくせず、参照の等価性に.ReferenceEquals()を使用するのではなく、なぜこのルートを選んだのかを考えていました。

後者のアプローチは混乱を招くためです。考慮してください:

if (null.ReferenceEquals(null)) System.out.println("ok");

このコードを印刷する"ok"必要がありNullPointerExceptionますか、それともスローする必要がありますか?


-2

JavaとC#の利点は、オブジェクト指向であることです。

パフォーマンスの観点からは、コードの記述も簡単である必要があります。OOPは論理的に異なる要素を異なるオブジェクトで表現することを目的としているため、オブジェクトが非常に大きくなる可能性があることを考慮して、参照の等価性のチェックがより高速になります。

論理的な観点から-オブジェクトの別のオブジェクトへの同等性は、オブジェクトの同等性のプロパティと比較するほど明白である必要はありません(例:null == nullは論理的にどのように解釈されますか?これはケースごとに異なります)。

要するに、「参照の平等よりも論理の平等が常に必要だ」というあなたの観察結果だと思います。言語設計者の間のコンセンサスはおそらく反対でした。幅広いプログラミング経験がないため、個人的にはこれを評価するのが難しいと感じています。おおまかに言って、最適化アルゴリズムでは参照の等式を、データセットの処理では論理の等式を使用します。


7
参照の平等はオブジェクトの方向とは関係ありません。実際、まったく逆です。オブジェクト指向の基本的な特性の1つは、同じ動作をするオブジェクトを区別できないことです。1つのオブジェクトが別のオブジェクトをシミュレートできる必要があります。(結局、OOはシミュレーションのために発明されました!)参照の等価性により、同じ動作を持つ2つの異なるオブジェクトを区別でき、シミュレートされたオブジェクトと実際のオブジェクトを区別できます。したがって、参照の平等オブジェクトの方向を壊します。OOプログラムで、参照の等価性を使用しないでください
ヨルグWミットタグ

@JörgWMittag:オブジェクト指向プログラムを適切に実行するには、オブジェクトXの状態がYの状態と等しいかどうかを尋ねる手段[一時的な状態の可能性]と、オブジェクトXがYと同等かどうかを尋ねる手段が必要[Xは、その状態が永遠にYと等しいことが保証されている場合にのみ、Yと同等です]。等価性と状態等価性に別々の仮想メソッドを持つことは良いことですが、多くのタイプでは、参照の不等価性は非等価性を意味し、それを証明するために仮想メソッドのディスパッチに時間を費やす理由はありません。
supercat

-3

.equals()変数をその内容で比較します。その代わりに==、内容によってオブジェクトを比較します...

オブジェクトを使用すると、より正確に使用できます .equals()


3
あなたの仮定は間違っています。.equals()は、.equals()がコーディングされているものは何でも行います。通常はコンテンツごとですが、そうである必要はありません。また、.equals()を使用する方が正確ではありません。それはあなたが何を達成しようとしているかに依存します。
ジッパー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.