从c++到Java,一个显而易见的未回答的问题是为什么Java没有包含操作符重载?
复合物不是a b c吗?A = b + c;比复合物a, b, c简单得多;A = b.add(c);?
是否有一个已知的原因,不允许操作符重载的有效参数?理由是随意的,还是被时间消磨了?
从c++到Java,一个显而易见的未回答的问题是为什么Java没有包含操作符重载?
复合物不是a b c吗?A = b + c;比复合物a, b, c简单得多;A = b.add(c);?
是否有一个已知的原因,不允许操作符重载的有效参数?理由是随意的,还是被时间消磨了?
当前回答
有很多帖子抱怨操作员超载。
我觉得我必须澄清“操作符重载”的概念,为这个概念提供另一种观点。
代码混淆?
这个论点是谬论。
混淆在所有语言中都是可能的……
在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接口:
您应该克隆实现此接口的对象。但你可以撒谎。并创建一个不同的对象。事实上,这个接口非常弱,你可以返回另一种类型的对象,只是为了好玩:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
由于克隆接口可能会被滥用/混淆,是否应该以c++操作符重载应该被禁止的同样理由禁止它?
我们可以重载MyComplexNumber类的toString()方法,让它返回一天中经过字符串化的时间。toString()重载也应该被禁止吗?我们可以破坏MyComplexNumber。等于让它返回一个随机值,修改操作数…等等,等等。
在Java中,就像在c++或其他语言中一样,程序员在编写代码时必须尊重最低限度的语义。这意味着实现一个add函数进行添加,一个Cloneable实现方法进行克隆,以及一个++操作符进行递增。
到底什么是混淆?
现在我们知道,即使通过原始的Java方法,代码也可以被破坏,我们可以问问自己,在c++中操作符重载的真正用途是什么?
清晰自然的符号:方法vs.操作符重载?
我们将在下面比较不同情况下,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对象,使用谷歌上找到的“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
这并不局限于矩阵。Java中的BigInteger和BigDecimal类也有同样令人困惑的冗长之处,而c++中的BigInteger和BigDecimal类就像内置类型一样清晰。
自然的迭代器:
// 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中没有接口)、标准对象(不可能有正确的接口)和用户定义对象。
例如,计算任意类型的两个值的平均值:
// 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语言中,操作符的实际行为将根据其操作数而改变。例如,两个整数相加不同于两个双精度数相加,甚至一个整数和一个双精度数相加。甚至还有整个指针算术域(在没有强制转换的情况下,您可以向指针添加一个整数,但不能添加两个指针……)
在Java中,没有指针算术,但有人仍然发现没有+操作符的字符串连接将是荒谬的,足以证明“操作符重载是邪恶的”信条中的异常。
只是你,作为一个C(历史原因)或Java(个人原因,见下文)编码器,你不能提供你自己的。
在c++中,操作符重载是不可选的…
在c++中,内置类型的操作符重载是不可能的(这是一件好事),但是用户定义类型可以有用户定义的操作符重载。
如前所述,与Java相反,在c++中,与内置类型相比,用户类型不被认为是语言的二等公民。因此,如果内置类型具有操作符,那么用户类型也应该能够具有操作符。
事实是,就像toString(), clone(), equals()方法是为Java(即准标准),c++操作符重载是c++的重要组成部分,它变得像原始的C操作符或前面提到的Java方法一样自然。
与模板编程相结合,操作符重载成为众所周知的设计模式。事实上,在STL中,如果不使用重载操作符,以及为自己的类重载操作符,就不能走得很远。
...但它不应被滥用
操作符重载应尽量尊重操作符的语义。不要在+运算符中使用减法(就像“不要在add函数中使用减法”,或者“在克隆方法中返回废话”一样)。
强制转换重载可能非常危险,因为它们可能导致歧义。所以它们应该被保留在明确定义的情况下。至于&&和||,除非你真的知道你在做什么,否则永远不要重载它们,因为你会失去本机操作符&&和||所享受的短路计算。
所以…好吧……那么为什么在Java中不可能呢?
因为詹姆斯·高斯林说过:
我没有使用操作符重载,这是我个人的选择,因为我见过太多的人在c++中滥用它。 詹姆斯·高斯林。来源:http://www.gotw.ca/publications/c_family_interview.htm
请将上面Gosling的文本与下面Stroustrup的文本进行比较:
许多c++设计决策都源于我不喜欢强迫人们以某种特定的方式做事[…]通常,我很想取缔我个人不喜欢的功能,但我克制自己不这么做,因为我认为我没有权利把自己的观点强加给别人。 内定。来源:c++的设计和发展(1.3通用背景)
操作符重载对Java有利吗?
一些对象将从操作符重载中获益良多(具体或数值类型,如BigDecimal、复数、矩阵、容器、迭代器、比较器、解析器等)。
在c++中,由于Stroustrup的谦逊,您可以从这个好处中获益。在爪哇,你被Gosling的个人选择搞砸了。
它能被添加到Java中吗?
现在不在Java中添加操作符重载的原因可能是内部政治、对特性的过敏、对开发人员的不信任(你知道,那些破坏者似乎一直困扰着Java团队……)、与以前的jvm的兼容性、编写正确规范的时间等等。
所以不要屏住呼吸等待这个功能…
但是他们是用c#做的!!
是的…
虽然这不是两种语言之间的唯一区别,但这一点总是让我很开心。
显然,c#的人,他们的“每个原语都是一个结构体,而结构体派生于对象”,在第一次尝试时就得到了正确的答案。
他们用其他语言也这么做!!
尽管所有的FUD都反对使用已定义的操作符重载,但以下语言支持它:Kotlin, Scala, Dart, Python, f#, c#, D, Algol 68, Smalltalk, Groovy, Raku(以前是Perl 6), c++, Ruby, Haskell, MATLAB, Eiffel, Lua, Clojure, Fortran 90, Swift, Ada, Delphi 2005…
如此多的语言,有如此多不同的(有时是相反的)哲学,但他们都同意这一点。
发人深思……
其他回答
Groovy具有操作符重载,并且运行在JVM中。如果您不介意性能损失(每天都在减小)。它是基于方法名自动生成的。例如,'+'调用'plus(参数)'方法。
假设Java是实现语言,那么a、b和c都是对初始值为null的Complex类型的引用。还假设Complex是不可变的,就像前面提到的BigInteger和类似的不可变BigDecimal一样,我想你是指下面的意思,因为你将引用赋值给从添加b和c返回的Complex,而不是将这个引用与a进行比较。
不是: 复合物a, b, c;A = b + c; 简单得多: 复合物a, b, c;A = b.add(c);
从技术上讲,每一种编程语言都有运算符重载,可以处理不同类型的数字,例如整数和实数。解释:术语重载意味着一个函数有多个实现。在大多数编程语言中,为运算符+提供了不同的实现,一个用于整数,一个用于实数,这被称为运算符重载。
Now, many people find it strange that Java has operator overloading for the operator + for adding strings together, and from a mathematical standpoint this would be strange indeed, but seen from a programming language's developer's standpoint, there is nothing wrong with adding builtin operator overloading for the operator + for other classes e.g. String. However, most people agree that once you add builtin overloading for + for String, then it is generally a good idea to provide this functionality for the developer as well.
我完全不同意操作符重载会混淆代码的谬论,因为这是由开发人员决定的。这是naïve的想法,老实说,它已经过时了。
+1用于在Java 8中添加操作符重载。
Java设计人员认为操作符重载带来的麻烦大于它的价值。就这么简单。
在一种每个对象变量实际上都是引用的语言中,运算符重载具有相当不合逻辑的额外危险——至少对c++程序员是这样。比较c#的==操作符重载和Object的情况。等号和对象。ReferenceEquals(或其他名称)。
有很多帖子抱怨操作员超载。
我觉得我必须澄清“操作符重载”的概念,为这个概念提供另一种观点。
代码混淆?
这个论点是谬论。
混淆在所有语言中都是可能的……
在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接口:
您应该克隆实现此接口的对象。但你可以撒谎。并创建一个不同的对象。事实上,这个接口非常弱,你可以返回另一种类型的对象,只是为了好玩:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
由于克隆接口可能会被滥用/混淆,是否应该以c++操作符重载应该被禁止的同样理由禁止它?
我们可以重载MyComplexNumber类的toString()方法,让它返回一天中经过字符串化的时间。toString()重载也应该被禁止吗?我们可以破坏MyComplexNumber。等于让它返回一个随机值,修改操作数…等等,等等。
在Java中,就像在c++或其他语言中一样,程序员在编写代码时必须尊重最低限度的语义。这意味着实现一个add函数进行添加,一个Cloneable实现方法进行克隆,以及一个++操作符进行递增。
到底什么是混淆?
现在我们知道,即使通过原始的Java方法,代码也可以被破坏,我们可以问问自己,在c++中操作符重载的真正用途是什么?
清晰自然的符号:方法vs.操作符重载?
我们将在下面比较不同情况下,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对象,使用谷歌上找到的“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
这并不局限于矩阵。Java中的BigInteger和BigDecimal类也有同样令人困惑的冗长之处,而c++中的BigInteger和BigDecimal类就像内置类型一样清晰。
自然的迭代器:
// 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中没有接口)、标准对象(不可能有正确的接口)和用户定义对象。
例如,计算任意类型的两个值的平均值:
// 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语言中,操作符的实际行为将根据其操作数而改变。例如,两个整数相加不同于两个双精度数相加,甚至一个整数和一个双精度数相加。甚至还有整个指针算术域(在没有强制转换的情况下,您可以向指针添加一个整数,但不能添加两个指针……)
在Java中,没有指针算术,但有人仍然发现没有+操作符的字符串连接将是荒谬的,足以证明“操作符重载是邪恶的”信条中的异常。
只是你,作为一个C(历史原因)或Java(个人原因,见下文)编码器,你不能提供你自己的。
在c++中,操作符重载是不可选的…
在c++中,内置类型的操作符重载是不可能的(这是一件好事),但是用户定义类型可以有用户定义的操作符重载。
如前所述,与Java相反,在c++中,与内置类型相比,用户类型不被认为是语言的二等公民。因此,如果内置类型具有操作符,那么用户类型也应该能够具有操作符。
事实是,就像toString(), clone(), equals()方法是为Java(即准标准),c++操作符重载是c++的重要组成部分,它变得像原始的C操作符或前面提到的Java方法一样自然。
与模板编程相结合,操作符重载成为众所周知的设计模式。事实上,在STL中,如果不使用重载操作符,以及为自己的类重载操作符,就不能走得很远。
...但它不应被滥用
操作符重载应尽量尊重操作符的语义。不要在+运算符中使用减法(就像“不要在add函数中使用减法”,或者“在克隆方法中返回废话”一样)。
强制转换重载可能非常危险,因为它们可能导致歧义。所以它们应该被保留在明确定义的情况下。至于&&和||,除非你真的知道你在做什么,否则永远不要重载它们,因为你会失去本机操作符&&和||所享受的短路计算。
所以…好吧……那么为什么在Java中不可能呢?
因为詹姆斯·高斯林说过:
我没有使用操作符重载,这是我个人的选择,因为我见过太多的人在c++中滥用它。 詹姆斯·高斯林。来源:http://www.gotw.ca/publications/c_family_interview.htm
请将上面Gosling的文本与下面Stroustrup的文本进行比较:
许多c++设计决策都源于我不喜欢强迫人们以某种特定的方式做事[…]通常,我很想取缔我个人不喜欢的功能,但我克制自己不这么做,因为我认为我没有权利把自己的观点强加给别人。 内定。来源:c++的设计和发展(1.3通用背景)
操作符重载对Java有利吗?
一些对象将从操作符重载中获益良多(具体或数值类型,如BigDecimal、复数、矩阵、容器、迭代器、比较器、解析器等)。
在c++中,由于Stroustrup的谦逊,您可以从这个好处中获益。在爪哇,你被Gosling的个人选择搞砸了。
它能被添加到Java中吗?
现在不在Java中添加操作符重载的原因可能是内部政治、对特性的过敏、对开发人员的不信任(你知道,那些破坏者似乎一直困扰着Java团队……)、与以前的jvm的兼容性、编写正确规范的时间等等。
所以不要屏住呼吸等待这个功能…
但是他们是用c#做的!!
是的…
虽然这不是两种语言之间的唯一区别,但这一点总是让我很开心。
显然,c#的人,他们的“每个原语都是一个结构体,而结构体派生于对象”,在第一次尝试时就得到了正确的答案。
他们用其他语言也这么做!!
尽管所有的FUD都反对使用已定义的操作符重载,但以下语言支持它:Kotlin, Scala, Dart, Python, f#, c#, D, Algol 68, Smalltalk, Groovy, Raku(以前是Perl 6), c++, Ruby, Haskell, MATLAB, Eiffel, Lua, Clojure, Fortran 90, Swift, Ada, Delphi 2005…
如此多的语言,有如此多不同的(有时是相反的)哲学,但他们都同意这一点。
发人深思……