C ++からJavaに移行すると、未回答の明らかな質問は、なぜJavaに演算子のオーバーロードが含まれていないのかということです。
Complex a, b, c; a = b + c;
よりもはるかに単純ではありませんComplex a, b, c; a = b.add(c);
か?
これの既知の理由はありますか?演算子のオーバーロードを許可しないための有効な引数はありますか?理由は恣意的ですか、それとも時間に遅れましたか?
C ++からJavaに移行すると、未回答の明らかな質問は、なぜJavaに演算子のオーバーロードが含まれていないのかということです。
Complex a, b, c; a = b + c;
よりもはるかに単純ではありませんComplex a, b, c; a = b.add(c);
か?
これの既知の理由はありますか?演算子のオーバーロードを許可しないための有効な引数はありますか?理由は恣意的ですか、それとも時間に遅れましたか?
回答:
によって参照されるオブジェクトの以前の値を上書きしたい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=
参照コピーを実行するためa
、b
現在は同じ値を参照しています。その結果、オブジェクトはそれ自体と等しいため、比較により「等しい」が生成されます。
コピーと参照の違いは、オペレーターのオーバーロードの混乱を増すだけです。@Sebastianが述べたように、JavaとC#はどちらも値と参照の等価性を別々にoperator+
処理する必要があります- 値とオブジェクトを処理する可能性がありますが、operator=
参照を処理するためにすでに実装されています。
C ++では、一度に1種類の比較のみを処理する必要があるため、混乱が少なくなります。例えば、上のComplex
、operator=
およびoperator==
両方の値に取り組んでいる-の値をコピーし、それぞれの値を比較します。
オペレーターの過負荷について不満を言う投稿がたくさんあります。
「オペレーターのオーバーロード」の概念を明確にし、この概念に別の視点を提供する必要があると感じました。
この議論は誤りです。
演算子のオーバーロードを介してC ++の場合と同様に、関数またはメソッドを介してCまたはJavaのコードを難読化するのは簡単です。
// 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 のCloneable
インターフェースを見てみましょう。
このインターフェースを実装するオブジェクトを複製することになっています。しかし、あなたはうそをつくことができました。そして、別のオブジェクトを作成します。実際、このインターフェースは非常に弱いため、おもしろいことに、別のタイプのオブジェクトをすべて返すことができます。
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Cloneable
インターフェイスは乱用/難読化される可能性があるため、C ++演算子のオーバーロードが想定されているのと同じ理由で禁止する必要がありますか?
クラスのtoString()
メソッドをオーバーロードしてMyComplexNumber
、文字列化された時間を返すようにすることができます。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 ;
演算子のオーバーロードが提供されている限り、AとBはC ++のどのタイプでもかまいません。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 ++では、演算子のオーバーロードにより、各コンテナーは同じ方法でコンテンツにアクセスします。
以下の例では、Matrix
Googleで見つかった「Java Matrixオブジェクト」と「C ++ 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
そして、これは行列に限定されません。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") ;
OK、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では、演算子の実際の動作は、そのオペランドに従って変化します。たとえば、2つの整数を追加することは、2つのdouble、または1つの整数と1つのdoubleを追加することとは異なります。ポインタ算術領域全体もあります(キャストを行わない場合、ポインタに整数を追加できますが、2つのポインタを追加することはできません...)。
Javaでは、ポインター算術はありませんが、+
演算子なしで文字列連結が見つかったとしても、「演算子の過負荷は悪である」という信条の例外を正当化するのに十分とんでもないことになります。
C(歴史的理由)またはJava(個人的理由、以下を参照)コーダーとして、自分でコードを提供することはできません。
C ++では、組み込み型の演算子オーバーロードは不可能です(これは良いことです)が、ユーザー定義型はユーザー定義の演算子オーバーロードを持つことができます。
すでに前に述べたように、C ++では、Javaとは逆に、組み込み型と比較した場合、ユーザー型は言語の二級市民とは見なされません。したがって、組み込み型に演算子がある場合、ユーザー型も演算子を持つことができるはずです。
真実はそれで、同様にtoString()
、clone()
、equals()
メソッドは、Javaのためのもの(すなわち、準標準のような)、C ++演算子のオーバーロードは、それが本来のC演算子、または前述のJavaメソッドとして自然のようになることをC ++のそんなに一部です。
テンプレートプログラミングと組み合わせると、演算子のオーバーロードはよく知られた設計パターンになります。実際、オーバーロードされた演算子を使用せずに、独自のクラスに演算子をオーバーロードしない限り、STLをはるかに超えることはできません。
オペレーターのオーバーロードは、オペレーターのセマンティクスを尊重するよう努めるべきです。+
演算子で減算しないでください(「add
関数で減算しない」、「clone
メソッドでがらくたを返す」など)。
キャストのオーバーロードはあいまいさにつながる可能性があるため、非常に危険です。したがって、これらは明確に定義されたケースのために予約する必要があります。あなたが本当に何をやっている知っている限り、あなたがネイティブオペレーターその短絡評価を失うことになるとして、これまで、それらをオーバーロードしていないとお楽しみください。&&
||
&&
||
ジェームズ・ゴズリングがそう言ったので:
C ++で乱用する人が多すぎるのを見たので、オペレーターのオーバーロードをかなり個人的な選択として省略しました。
ジェームズ・ゴズリング。出典:http : //www.gotw.ca/publications/c_family_interview.htm
上のGoslingのテキストと下のStroustrupのテキストを比較してください。
ので、多くのC ++の設計上の決定は、いくつかの特定の方法で物事を行う人々を強制するために、私の嫌いで自分のルーツを持っている[...]多くの場合、私は私が個人的に嫌わ機能を非合法化するように誘惑された、私はそうすることを控え、私は私が持っていたとは思いませんでした他人に私の見解を強制する権利。
Bjarne Stroustrup。出典:C ++の設計と進化(1.3一般的な背景)
一部のオブジェクトは、演算子のオーバーロード(BigDecimal、複素数、行列、コンテナー、イテレーター、コンパレーター、パーサーなどの具体的な数値型)から大きなメリットを得ます。
C ++では、Stroustrupの謙虚さのために、この利点から利益を得ることができます。Javaでは、ゴスリングの個人的な選択のために、単純に頭を悩ませています。
現在Javaでオペレーターのオーバーロードを追加しない理由は、内部政治、機能へのアレルギー、開発者への不信感(Javaチームを悩ませていると思われる妨害行為など)、以前のJVMとの互換性、正しい仕様書などを書く時間
だから、この機能を待って息を止めないでください...
うん...
これは2つの言語の唯一の違いではありませんが、これは私を楽しませるのに失敗しません。
どうやら、C#の人々は、「すべてのプリミティブはstruct
であり、struct
Objectから派生している」ので、最初に試してみました。
使用される定義済み演算子のオーバーロードに対するすべてのFUDにもかかわらず、Scala、Dart、Python、F#、C#、D、Algol 68、Smalltalk、Groovy、Perl 6、C ++、Ruby、Haskell、MATLAB、Eiffel、Lua、Clojure、Fortran 90、Swift、Ada、Delphi 2005 ...
非常に多くの言語があり、非常に多くの異なる(時には反対の)哲学がありますが、それでもそれらはすべてその点に同意します。
思考の糧...
James Goslingは、Javaの設計を次のように説明しました。
「あるアパートから別のアパートに移動するときの移動には、この原則があります。興味深い実験は、アパートを梱包してすべてを箱に入れ、次のアパートに移動し、必要になるまで何も開梱しないことです。だから、あなたは最初の食事を作り直すと、箱から何かを取り出します。その後、1か月ほど経つと、それを使用して、実際に必要なものをほぼ把握し、残りの食事を取ります。もの-どれだけ好きか、どれだけクールかを忘れて、ただ捨てるだけです。それがあなたの人生を簡素化し、あらゆる種類の設計問題でその原則を使用できるのは驚くべきことです。 「かっこいいか、単に面白いから」
基本的に演算子のオーバーロードは、ある種のポイント、通貨、または複素数をモデル化するクラスに最適です。しかしその後、あなたはすぐに例を使い果たし始めます。
もう1つの要因は、「&&」、「||」、キャスト演算子、そしてもちろん「new」などの演算子をオーバーロードする開発者によるC ++の機能の悪用でした。これと値渡しおよび例外との組み合わせによる複雑さについては、Exceptional C ++ブックで詳しく説明されています。
Many C++ design decisions have their roots in my dislike for forcing people to do things in some particular way [...] Often, I was tempted to outlaw a feature I personally disliked, I refrained from doing so because I did not think I had the right to force my views on others. (B. Stroustrup)
。
I'd like them to go have a look at some C++ code out there that is hideously put together with weird hacks and "exceptional" features of the language
:悪いプログラマーは言語に関係なく悪いコードを書くでしょう。アイデアを得るために、Javaの関数パラメーターの「参照渡し」をエミュレートしてみてください。私はコードを見て、痛いほど笑いました。これは、Goslingが使用しなかったものです。したがって、Javaに恐ろしいハックが必要でしたが、C#とC ++の両方で、ゼロコストでネイティブに存在しています。
Boost.Unitsを確認してください:リンクテキスト
オペレーターのオーバーロードにより、オーバーヘッドのない次元分析を提供します。これでどの程度明確になるでしょうか?
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」が出力されますが、これは正しいです。
Javaの設計者たちは、オペレーターのオーバーロードは価値があるよりも問題であると判断しました。そのような単純な。
すべてのオブジェクト変数が実際に参照である言語では、演算子のオーバーロードは、少なくともC ++プログラマにとって、まったく非論理的であるという追加の危険を伴います。C#の==演算子のオーバーロードand Object.Equals
およびObject.ReferenceEquals
(またはそれが呼び出されたもの)と状況を比較します。
Groovyにはオペレーターのオーバーロードがあり、JVMで実行されます。パフォーマンスへの影響を気にしない場合(毎日小さくなります)。メソッド名に基づいて自動的に行われます。たとえば、「+」は「plus(argument)」メソッドを呼び出します。
where ...
なる.Where(i => ...
)で適切なアプローチを採用しました。算術演算子で同じことをしたとしたら、多くのことがより簡単で強力になります。Javaには白紙の状態の利点があり、これを正しく行うことができます(ただし、宗教的な理由から、おそらくそうなることは決してありません)。
これは、開発者に名前が意図を明確に伝える関数を作成するように強いる設計上の選択だったのではないかと思います。C ++では、開発者は特定の演算子の一般に受け入れられている性質とは関係のない機能で演算子をオーバーロードするため、演算子の定義を見ないでコードの一部を実行することはほぼ不可能です。
In C++ developers would overload operators with functionality that would often have no relation to the commonly accepted nature of the given operator
:これは不当な主張です。私は12年以上プロのC ++開発者であり、この問題に遭遇することはめったにありません。実際、C ++で見たバグと設計エラーのほとんどは、Cスタイルのコード(void *
、キャストなど)にありました
add
関数が本当に誤用される可能性があること(乗算やmutexの取得など)についても触れていません... user14128によって言及されている乱用は、演算子に限定されていませんが、演算子のオーバーロードに関して、C対C ++の初期の段階から来ると思われる一種の病理学的恐怖があります。Javaに変更されずにそのまま入った恐怖ですが、ありがたいことに、C#には入っていませんでした...最後に、セマンティクスを尊重します明確な関数/演算子を書くことは開発者の仕事です。言語ではありません。
cout << f() || g();
括弧は、それを明確にするのではなく、正しいものにします。そして、ビットシフト演算子が悪用されていなかったので、それらは必要ありませんでした。なぜcout << (5&3) << endl;
より良いですcout.fmt(5&3)(endl);
か?ファンクターメンバー変数で関数呼び出し演算子を使用すると、グリフが見栄えが良いという理由だけで、ビットごとの演算子を転用するよりもストリームの設計が無限に良くなります。しかし、これはストリームの唯一の問題とはほど遠いものです。
まあ、あなたは本当にオペレーターのオーバーロードで足元を撃つことができます。ポインターを使って愚かな間違いをするようなもので、はさみを取り除くことになりました。
少なくともそれが理由だと思います。とにかく私はあなたの味方です。:)
演算子のオーバーロードが、その演算子が操作ロジックと一致しないタイプの論理エラーを引き起こすと言うと、それは何も言わないようなものです。関数名が操作ロジックに不適切な場合も、同じタイプのエラーが発生します。解決策は何ですか。関数の使用能力を落とします!?これはコミカルな答えです-「操作ロジックには不適切」、すべてのパラメーター名、すべてのクラス、関数など、論理的に不適切である可能性があるもの。このオプションは、立派なプログラミング言語で利用できるはずだと思いますし、安全ではないと思う人もいるでしょう。C#を見てみましょう。彼らはポインタを垂れ流しましたが、ちょっと-「安全でないコード」ステートメントがあります-自分のリスクで好きなようにプログラムしてください。
技術的には、整数や実数など、さまざまなタイプの数値を処理できるすべてのプログラミング言語に演算子のオーバーロードがあります。説明:オーバーロードという用語は、1つの関数に対して複数の実装が存在することを意味します。ほとんどのプログラミング言語では、演算子+、整数用、実数用のさまざまな実装が提供されています。これは演算子オーバーロードと呼ばれます。
さて、多くの人々は、Javaが演算子+に対して演算子のオーバーロードを行い、文字列を一緒に追加するのが奇妙であると感じています。数学的な観点からは、これは実際には奇妙ですが、プログラミング言語の開発者の観点から見ると、組み込みの演算子のオーバーロードを追加することに問題はありません。演算子+、他のクラス(例:String)。ただし、文字列の+に組み込みのオーバーロードを追加したら、開発者にもこの機能を提供することをお勧めします。
これは、オペレーターがコードをオーバーロードしてコードを難読化するという誤解に完全に同意しません。これは単純な考え方で、正直に言うと古くなっています。
Java 8で演算子のオーバーロードを追加するための+1
+
文字列っぽいものを何でも連結するためのJavaの使用は非常に恐ろしいです/
。CおよびFORTRANでの全体および部分的な除算のオーバーロードも同様です。パスカルの多くのバージョンでは、任意の数値型の算術演算子の使用はにオペランドをキャストすると数値的に同等の結果が得られますReal
全体の数字ではないかもしれない結果を介して供給されなければならないものの、Trunc
またはRound
、彼らは整数に割り当てることができます前に。
Javaを実装言語とすると、a、b、cはすべて、初期値がnullのComplex型への参照になります。また、Complexが前述のBigIntegerおよび同様の不変のBigDecimalとして不変であると仮定すると、bとcを追加して返されたComplexへの参照を割り当てており、この参照をaと比較していないため、次のことを意味すると思います。
ない:
Complex a, b, c; a = b + c;
よりもはるかに単純です:
Complex a, b, c; a = b.add(c);
場合によっては、演算子のオーバーロード、フレンドクラス、および多重継承が便利です。
しかし、それでも良い決断だったと思います。Javaに演算子のオーバーロードがあったとしたら、ソースコードを調べない限り、演算子の意味を確信することはできません。現時点では必要ありません。また、演算子のオーバーロードの代わりにメソッドを使用する例も読みやすいと思います。物事をより明確にしたい場合は、常に毛深い声明の上にコメントを追加できます。
// a = b + c
Complex a, b, c; a = b.add(c);
これは、これを拒否する正当な理由ではありませんが、実際的な理由です。
人々は常に責任を持ってそれを使用するとは限りません。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つ以上のデフォルトフィールドを持つことができます。(あなたはまだあなたが望む値を与えることができます)。文字列をrawレイヤーとして使用できます。
Javaオペレーターオーバーロードのネイティブサポートの代替
Javaには演算子のオーバーロードがないため、次のような代替手段を検討できます。
誰かが他の人を知っている場合はコメントしてください。私はそれをこのリストに追加します。
Java言語は演算子のオーバーロードを直接サポートしていませんが、任意のJavaプロジェクトでManifoldコンパイラプラグインを使用して有効にすることができます。Java 8-13(現在のJavaバージョン)をサポートし、IntelliJ IDEAで完全にサポートされています。