web-dev-qa-db-ja.com

Javaが演算子のオーバーロードを提供しないのはなぜですか?

C++からJavaに移行すると、明らかな未回答の質問は、なぜJavaに演算子のオーバーロードが含まれないのかということです。

Complex a, b, c; a = b + c;Complex a, b, c; a = b.add(c);よりもはるかに単純ではありませんか?

これには既知の理由がありますか?notの有効な引数は演算子のオーバーロードを許可しますか?その理由はarbitrary意的ですか、または時間の経過とともに失われましたか?

384
rengolin

aによって参照されるオブジェクトの以前の値を上書きする場合、メンバー関数を呼び出す必要があります。

Complex a, b, c;
// ...
a = b.add(c);

C++では、この式はコンパイラーに、スタック上に3つのオブジェクトを作成し、加算を実行し、コピー一時オブジェクトから既存のオブジェクトaへの結果値を指示します。

ただし、Javaでは、operator=は参照型の値のコピーを実行せず、ユーザーは値型ではなく新しい参照型のみを作成できます。したがって、Complexという名前のユーザー定義型の場合、割り当てとは、既存の値への参照をコピーすることです。

代わりに考慮してください:

b.set(1, 0); // initialize to real number '1'
a = b; 
b.set(2, 0);
assert( !a.equals(b) ); // this assertion will fail

C++では、これにより値がコピーされるため、比較の結果は等しくなくなります。 Javaでは、operator=は参照コピーを実行するため、abは同じ値を参照しています。その結果、オブジェクトはそれ自体と等しいため、比較は「等しい」を生成します。

コピーと参照の違いは、演算子のオーバーロードの混乱を増やすだけです。 @Sebastianが述べたように、JavaとC#は両方とも値と参照の等価性を別々に処理する必要があります-operator+はおそらく値とオブジェクトを処理しますが、operator=は参照を処理するために既に実装されています。

C++では、一度に1種類の比較のみを処理する必要があるため、混乱が少なくなります。たとえば、Complexでは、operator=operator==は両方とも値を処理しています(値のコピーと値の比較)。

17
Aaron

オペレーターの過負荷について不満を言う投稿がたくさんあります。

「演算子の過負荷」の概念を明確にし、この概念に関する別の視点を提供しなければならないと感じました。

コードの難読化?

この議論は誤りです。

難読化はすべての言語で可能です...

関数またはメソッドを使用してCまたはJavaのコードを難読化するのは、演算子のオーバーロードを使用してC++の場合と同様に簡単です。

// C++
T operator + (const T & a, const T & b) // add ?
{
   T c ;
   c.value = a.value - b.value ; // subtract !!!
   return c ;
}

// Java
static T add (T a, T b) // add ?
{
   T c = new T() ;
   c.value = a.value - b.value ; // subtract !!!
   return c ;
}

/* C */
T add (T a, T b) /* add ? */
{
   T c ;
   c.value = a.value - b.value ; /* subtract !!! */
   return c ;
}

... Javaの標準インターフェースでも

別の例として、Javaで Cloneable interface を見てみましょう。

このインターフェイスを実装するオブジェクトを複製することになっています。しかし、あなたはうそをつくことができます。そして、別のオブジェクトを作成します。実際、このインターフェイスは非常に弱いため、別のタイプのオブジェクトを完全に返すことができます。

class MySincereHandShake implements Cloneable
{
    public Object clone()
    {
       return new MyVengefulKickInYourHead() ;
    }
}

Cloneableインターフェイスは悪用/難読化される可能性があるため、C++演算子のオーバーロードと同じ理由で禁止する必要がありますか?

MyComplexNumberクラスのtoString()メソッドをオーバーロードして、文字列化された時刻を返すようにすることができます。 toString()オーバーロードも禁止する必要がありますか?サボタージュMyComplexNumber.equalsを使用して、ランダムな値を返したり、オペランドを変更したりすることができます...などなど。

Java、C++などの言語では、プログラマーはコードを記述する際に最小限のセマンティクスを尊重する必要があります。これは、追加するadd関数、および複製するCloneable実装メソッド、およびインクリメントよりも_++演算子を実装することを意味します。

とにかく難読化されているのは何ですか?

純粋なJavaメソッドを使用してもコードが妨害される可能性があることがわかったので、C++での演算子のオーバーロードの実際の使用について尋ねることができますか?

明確で自然な表記法:メソッドと演算子のオーバーロード?

以下では、JavaとC++の「同じ」コードをさまざまなケースで比較して、どの種類のコーディングスタイルがより明確であるかを考えます。

自然な比較:

// C++ comparison for built-ins and user-defined types
bool    isEqual          = A == B ;
bool    isNotEqual       = A != B ;
bool    isLesser         = A <  B ;
bool    isLesserOrEqual  = A <= B ;

// Java comparison for user-defined types
boolean isEqual          = A.equals(B) ;
boolean isNotEqual       = ! A.equals(B) ;
boolean isLesser         = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual  = A.comparesTo(B) <= 0 ;

演算子のオーバーロードが提供されている限り、C++ではAとBはどの型でもかまいません。 Javaでは、AとBがプリミティブではない場合、プリミティブのようなオブジェクト(BigIntegerなど)であってもコードが非常に混乱する可能性があります...

自然配列/コンテナアクセサと添え字:

// C++ container accessors, more natural
value        = myArray[25] ;         // subscript operator
value        = myVector[25] ;        // subscript operator
value        = myString[25] ;        // subscript operator
value        = myMap["25"] ;         // subscript operator
myArray[25]  = value ;               // subscript operator
myVector[25] = value ;               // subscript operator
myString[25] = value ;               // subscript operator
myMap["25"]  = value ;               // subscript operator

// Java container accessors, each one has its special notation
value        = myArray[25] ;         // subscript operator
value        = myVector.get(25) ;    // method get
value        = myString.charAt(25) ; // method charAt
value        = myMap.get("25") ;     // method get
myArray[25]  = value ;               // subscript operator
myVector.set(25, value) ;            // method set
myMap.put("25", value) ;             // method put

Javaでは、各コンテナが同じことを行う(インデックスまたは識別子を介してコンテンツにアクセスする)ために、別の方法があり、混乱を招くことがわかります。

C++では、オペレーターのオーバーロードのおかげで、各コンテナーは同じ方法を使用してそのコンテンツにアクセスします。

自然な高度なタイプの操作

以下の例では、「 Java Matrix object 」および「 c ++ Matrix object 」に対してGoogleで最初に見つかったリンクを使用して見つかったMatrixオブジェクトを使用します。

// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E =  A * (B / 2) ;
E += (A - B) * (C + D) ;
F =  E ;                  // deep copy of the matrix

// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ;            // deep copy of the matrix

そして、これは行列に限定されません。 JavaのBigIntegerクラスとBigDecimalクラスは同じ混乱する冗長性の影響を受けますが、C++での同等のものは組み込み型と同じくらい明確です。

自然反復子:

// C++ Random Access iterators
++it ;                  // move to the next item
--it ;                  // move to the previous item
it += 5 ;               // move to the next 5th item (random access)
value = *it ;           // gets the value of the current item
*it = 3.1415 ;          // sets the value 3.1415 to the current item
(*it).foo() ;           // call method foo() of the current item

// Java ListIterator<E> "bi-directional" iterators
value = it.next() ;     // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ;        // sets the value 3.1415 to the current item

自然ファンクター:

// C++ Functors
myFunctorObject("Hello World", 42) ;

// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;

テキストの連結:

// C++ stream handling (with the << operator)
                    stringStream   << "Hello " << 25 << " World" ;
                    fileStream     << "Hello " << 25 << " World" ;
                    outputStream   << "Hello " << 25 << " World" ;
                    networkStream  << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;

// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;

わかりました、JavaではMyString = "Hello " + 25 + " World" ;も使用できます...しかし、ちょっと待ってください。これは演算子のオーバーロードですよね。浮気じゃない?

:-D

汎用コード?

オペランドを変更する同じ汎用コードは、組み込み/プリミティブ(Javaにインターフェースを持たない)、標準オブジェクト(正しいインターフェースを持たない可能性がある)、およびユーザー定義オブジェクトの両方で使用できるはずです。

たとえば、任意のタイプの2つの値の平均値を計算します。

// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
   return (p_lhs + p_rhs) / 2 ;
}

int     intValue     = getAverage(25, 42) ;
double  doubleValue  = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix  matrixValue  = getAverage(mA, mB) ; // mA, mB are Matrix

// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.

オペレーターのオーバーロードについて議論する

演算子のオーバーロードを使用するC++コードとJavaの同じコードの公正な比較を見たので、「演算子のオーバーロード」を概念として説明できます。

コンピューターの前からオペレーターのオーバーロードが存在していました

コンピューターサイエンス以外でも、演算子のオーバーロードがあります。たとえば、数学では、+-*などの演算子がオーバーロードされます。

実際、+-*などの意味は、オペランドのタイプ(数値、ベクトル、量子波動関数、行列など)によって異なります。

私たちのほとんどは、科学コースの一環として、オペランドのタイプに応じて、演算子の複数の意味を学びました。彼らは混乱していると感じましたか?

演算子のオーバーロードは、そのオペランドに依存します

これは、演算子のオーバーロードの最も重要な部分です。数学や物理学のように、演算はオペランドの型に依存します。

したがって、オペランドのタイプを知っていれば、操作の効果がわかります。

CとJavaでも(ハードコードされた)演算子のオーバーロードがあります

Cでは、演算子の実際の動作は、オペランドに応じて変化します。たとえば、2つの整数を追加することは、2つのdoubleを追加すること、または1つの整数と1つのdoubleを追加することとは異なります。ポインター算術ドメイン全体もあります(キャストすることなく、ポインターに整数を追加できますが、2つのポインターを追加することはできません...)。

Javaにはポインター演算はありませんが、+演算子を使用せずに文字列の連結が見つかった場合、「演算子のオーバーロードは悪」という信条の例外を正当化するのに十分です。

C(歴史的な理由から)またはJava( 個人的な理由、以下を参照)コーダー、独自のものを提供することはできません。

C++では、演算子のオーバーロードはオプションではありません...

C++では、組み込み型の演算子のオーバーロードは不可能です(これは良いことです)が、 ユーザー定義の タイプは持つことができます ユーザー定義の 演算子のオーバーロード。

すでに前述したように、C++では、Javaとは反対に、組み込み型と比較すると、ユーザー型は言語の二流市民とは見なされません。したがって、組み込み型に演算子がある場合、ユーザー型にも演算子を含めることができます。

真実は、toString()clone()equals()メソッドがJava用であるということです(すなわち準標準的)、C++演算子のオーバーロードはC++の一部であるため、元のC演算子または前述のJavaメソッドと同じくらい自然になります。

テンプレートプログラミングと組み合わせると、演算子のオーバーロードはよく知られた設計パターンになります。実際、オーバーロードされた演算子を使用したり、独自のクラスの演算子をオーバーロードしたりせずに、STLにあまり遠くまで行くことはできません。

...しかし、悪用されるべきではありません

演算子のオーバーロードは、演算子のセマンティクスを尊重するよう努める必要があります。 +演算子で減算しないでください(「add関数で減算しない」、または「cloneメソッドでがらくたを返す」)。

キャストのオーバーロードは、あいまいさを招く可能性があるため、非常に危険です。したがって、それらは明確に定義されたケースのために本当に予約されるべきです。 &&||に関しては、ネイティブオペレーターの&&||が楽しむ短絡評価が失われるので、自分が何をしているかを本当に理解していない限り、それらをオーバーロードしないでください。

それで... OK ...では、なぜJavaでは不可能なのでしょうか?

ジェームズ・ゴスリングがそう言ったから:

演算子のオーバーロードを除外しました かなり個人的な選択 C++で乱用している人が多すぎたからです。

ジェームズ・ゴスリング。ソース: http://www.gotw.ca/publications/c_family_interview.htm

上記のGoslingのテキストと、以下のStroustrupのテキストを比較してください。

多くのC++設計の決定は、特定の方法で人々に何かをさせることを嫌う私の嫌いに根ざしています[...]しばしば、私は個人的に嫌いな機能を違法にしたいと思われました。 自分の意見を他人に押し付ける権利があるとは思わなかった

Bjarne Stroustrup。出典:C++の設計と進化(1.3一般的な背景)

演算子のオーバーロードはJavaにメリットがありますか?

一部のオブジェクトは、演算子のオーバーロード(BigDecimal、複素数、行列、コンテナ、イテレータ、コンパレータ、パーサーなどのような、具体的なまたは数値型)から大きな恩恵を受けます。

C++では、Stroustrupの謙虚さのためにこの利点を活用できます。 Javaでは、Goslingの 個人的な選択

Javaに追加できますか?

Javaで演算子のオーバーロードを追加しない理由は、内部の政治、機能に対するアレルギー、開発者の不信(ご存知のように、Javaチームを悩ませているように見える妨害者...)、以前のJVMとの互換性、正しい仕様などを書く時間.

この機能を待って息を止めないでください...

しかし、彼らはC#でそれをします!!!

うん...

これは2つの言語の唯一の違いではありませんが、この言語が私を楽しませることは決してありません。

どうやら、C#の人々は、 「すべてのプリミティブはstructであり、structはObjectから派生しています」、最初の試みでそれを正しくしました。

そして、彼らはそれを 他の言語 !!!

使用済みの定義済み演算子のオーバーロードに対するすべてのFUDにもかかわらず、次の言語がサポートしています: ScalaDartPythonF# =、 C#DALGOL 68スモールトークGroovyPerl 6 、C++、 RubyHaskellMATLABEiffelLuaClojureFortran 9SwiftAdaDelphi 2005 ...

非常に多くの異なる哲学(時には対立する哲学)を備えた非常に多くの言語がありますが、それらはすべてその点に同意しています。

思考の糧...

753
paercebal

James Goslingは、Javaの設計を次のように例えています。

「あるアパートから別のアパートに移動するときの移動には、この原則があります。興味深い実験は、アパートをまとめて箱に入れ、次のアパートに移動し、必要になるまで何も開梱しないことです。最初の食事を作り直し、箱から何かを取り出します。その後、1か月かそこらを使って、実際にあなたが実際に必要としているものを把握し、残りの部分を取ります。あなたがどれだけ好きか、どれだけクールかを忘れて、あなたはそれを捨てるだけです。それがあなたの人生をどのように単純化するかは驚くべきことです。 「かっこいい、または単に面白いから」

ここで引用のコンテキスト を読むことができます

基本的に、演算子のオーバーロードは、ある種のポイント、通貨、または複素数をモデル化するクラスに最適です。しかし、その後すぐにサンプルが不足し始めます。

別の要因は、「&&」、「||」、キャスト演算子、そしてもちろん「new」などの演算子をオーバーロードする開発者によるC++の機能の悪用です。これを値渡しと例外と組み合わせることで生じる複雑さは、 Exceptional C++ 本で十分にカバーされています。

42
Garth Gilmour

Boost.Unitsを確認してください: link text

オペレーターのオーバーロードにより、オーバーヘッドのないディメンション分析を提供します。これはどれほど明確になりますか?

quantity<force>     F = 2.0*newton;
quantity<length>    dx = 2.0*meter;
quantity<energy>    E = F * dx;
std::cout << "Energy = " << E << endl;

実際には「Energy = 4 J」と出力されますが、これは正しいです。

22
user15793

Javaの設計者は、オペレーターのオーバーロードは価値があるよりも厄介だと判断しました。そのような単純な。

すべてのオブジェクト変数が実際に参照である言語では、演算子のオーバーロードは、少なくともC++プログラマーにとっては非常に非論理的であるという追加の危険をもたらします。 C#の==演算子のオーバーロードとObject.EqualsおよびObject.ReferenceEquals(またはそれが呼び出されたもの)と状況を比較してください。

11
Sebastian Redl

Groovy には演算子のオーバーロードがあり、JVMで実行されます。パフォーマンスの低下を気にしない場合(毎日小さくなります)。メソッド名に基づいて自動的に行われます。たとえば、「+」は「plus(argument)」メソッドを呼び出します。

8
noah

これは、名前が意図を明確に伝える機能を開発者に強制するための意識的な設計選択だったと思います。 C++の開発者は、与えられた演算子の一般に受け入れられている性質としばしば関係のない機能で演算子をオーバーロードし、演算子の定義を見ずにコードの一部を決定することをほぼ不可能にします。

6
user14128

まあ、あなたは本当に演算子オーバーロードで足で自分自身を撃つことができます。ポインタを使って人々が愚かな間違いをするようなもので、ハサミを持ち去ることになりました。

少なくともそれが理由だと思う。とにかくあなたのそばにいます。 :)

5
Sarien

Javaでの演算子のオーバーロードは難読化につながると言う人もいます。それらの人々は、BigDecimalを使用して金銭的価値をパーセンテージで増やすなど、いくつかの基本的な数学を行うJavaコードを見ることをやめたことがありますか? ....そのようなエクササイズの冗長性は、難読化の独自のデモンストレーションになります。皮肉なことに、Javaに演算子のオーバーロードを追加すると、独自のCurrencyクラスを作成して、そのような数学的コードをエレガントでシンプルにすることができます(難読化は少なくなります)。

4
Volksman

技術的には、さまざまな種類の数値を処理できるすべてのプログラミング言語に演算子のオーバーロードがあります。整数および実数。説明:オーバーロードという用語は、1つの機能に対して複数の実装が存在することを意味します。ほとんどのプログラミング言語では、演算子+、整数用、実数用に異なる実装が提供されています。これは演算子のオーバーロードと呼ばれます。

今、多くの人々は、Javaが演算子+の文字列を追加するための演算子オーバーロードを持っていることを奇妙に感じています、そして数学的な観点からこれは確かに奇妙になりますが、プログラミング言語の開発者の観点から見ると、組み込みの演算子オーバーロードの追加には何の問題もありません演算子+他のクラスなど文字列。ただし、Stringの+に組み込みのオーバーロードを追加したら、開発者にもこの機能を提供することをお勧めします。

これは、開発者が決定するために残されているため、オペレーターのオーバーロードはコードを難読化するという誤解に完全に同意しません。これは考えるのがナイーブであり、非常に正直に言うと、古くなっています。

Java 8で演算子のオーバーロードを追加するための+1。

4
Olai

演算子のオーバーロードは、演算子が操作ロジックと一致しないタイプの論理エラーにつながると言って、何も言わないようなものです。関数名が操作ロジックに不適切な場合、同じタイプのエラーが発生します。そのため、解決策は次のとおりです。関数の使用能力を落とす!?これは滑comicな答えです-「操作ロジックには不適切」、すべてのパラメーター名、すべてのクラス、関数、または論理的に不適切なもの。私は、このオプションは立派なプログラミング言語で利用可能であるべきだと思います、そして、それが安全でないと思う人々-あなたがそれを使用しなければならないと言ってはいけません。 C#を見てみましょう。彼らはポインタを垂らしましたが、ちょっと-「安全でないコード」ステートメントがあります-あなた自身のリスクで好きなようにプログラムしてください。

4
Kvant

Javaを実装言語とすると、a、b、およびcはすべて、nullの初期値を持つComplex型への参照になります。前述の BigInteger および同様の不変 BigDecimal のようにComplexが不変であると仮定すると、返されるComplexへの参照を割り当てているので、次のことを意味すると思いますbとcを追加し、この参照をaと比較しないこと。

ではない:

Complex a, b, c; a = b + c;

much以下より簡単:

Complex a, b, c; a = b.add(c);
2

演算子のオーバーロード、フレンドクラス、多重継承があると便利な場合があります。

しかし、私はまだ良い決断だったと思います。 Javaに演算子のオーバーロードがあった場合、ソースコードを調べずに演算子の意味を確認することはできませんでした。現時点では必要ありません。また、演算子のオーバーロードの代わりにメソッドを使用するあなたの例も非常に読みやすいと思います。物事をより明確にしたい場合は、毛むくじゃらの文の上に常にコメントを追加することができます。

// a = b + c
Complex a, b, c; a = b.add(c);
1
user14070

Javaオペレーターのオーバーロードのネイティブサポートの代替

Javaには演算子のオーバーロードがないため、次の選択肢を検討できます。

  1. 別の言語を使用してください。 GroovyScala の両方に演算子のオーバーロードがあり、Javaに基づいています。
  2. Java-oo を使用します。これは、Javaでオペレーターのオーバーロードを有効にするプラグインです。プラットフォームに依存しないことに注意してください。また、多くの問題があり、Javaの最新リリース(つまり、Java 10)と互換性がありません。 ( 元のStackOverflowソース
  3. JNI 、Java Native Interface、または代替手段を使用します。これにより、Javaで使用するためのCまたはC++(他の場合もありますか?)メソッドを作成できます。もちろん、これはプラットフォームに依存しません。

誰かが他の人を知っているなら、コメントしてください、そして、私はそれをこのリストに加えます。

0
gagarwa

これはそれを拒否する正当な理由ではなく、実用的な理由です。

人々は常に責任を持ってそれを使用するとは限りません。 Pythonライブラリscapyのこの例を見てください:

>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>

説明は次のとおりです。

/演算子は、2つのレイヤー間の合成演算子として使用されています。これを行うと、下位層は、上位層に従って1つ以上のデフォルトフィールドをオーバーロードできます。 (あなたはまだあなたが望む値を与えることができます)。文字列は生のレイヤーとして使用できます。

0
Sarien