为什么Java不提供运算符重载?

从C ++到Java,一个明显的未解决的问题是为什么Java没有包含运算符重载?

不是Complex a, b, c; a = b + c; Complex a, b, c; a = b + c;Complex a, b, c; a = b.add(c);简单得多Complex a, b, c; a = b.add(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) );

在C ++中,这会复制该值,所以比较结果不会相等。 在Java中, operator=执行引用复制,所以ab现在引用相同的值。 因此,比较会产生“相等”,因为对象将与自身相等。

副本和引用之间的差异只会增加运算符重载的混淆。 正如@Sebastian所说,Java和C#都必须分别处理值和引用的相等性 - operator+可能会处理值和对象,但operator=已经实现来处理引用。

在C ++中,你只应该一次处理一种比较,所以它可能不那么令人困惑。 例如,在Complexoperator=operator==都处理值 - 分别复制值和比较值。


有很多帖子抱怨运营商超载。

我觉得我不得不澄清“运营商超载”的概念,为这个概念提供了另一种观点。

代码混淆?

这个论点是一个谬论。

所有语言都可以进行混淆...

通过函数/方法在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() ;
    }
}

由于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 ;

请注意,只要提供了运算符重载,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矩阵对象”和“c ++矩阵对象”:

// 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的BigIntegerBigDecimal类遭受相同的混淆冗长,而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中没有接口),也适用于标准对象(不能具有正确的接口)和用户定义的对象。

例如,计算任意类型的两个值的平均值:

// 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 ++中,内置类型的操作符重载是不可能的(这是一件好事),但用户定义的类型可以具有用户定义的操作符重载。

如前所述,在C ++中,与Java相反,与内置类型相比,用户类型不被视为语言的二级公民。 所以,如果内置类型有操作符,用户类型也应该能够拥有它们。

事实是,像toString()clone()equals()方法是针对Java的(即类似准标准的),C ++运算符重载是C ++的重要组成部分,因此它变得和原始C运算符一样自然或前面提到的Java方法。

结合模板编程,运算符重载成为众所周知的设计模式。 实际上,如果不使用重载运算符,并且为自己的类重载运算符,则不能在STL中走得太远。

...但不应该被滥用

运算符重载应力求尊重运算符的语义。 不要在+运算符中减去(如“在add函数中不减去”或“在clone方法中返回垃圾”)。

演员重载可能会非常危险,因为他们可能会导致歧义。 所以他们应该被保留用于明确定义的情况。 至于&&|| ,除非你真的知道你在做什么,否则不要重载他们,因为你将失去对本地运营商&&||短路评估。 请享用。

所以......好吧......那为什么在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#中做到了!

是啊...

虽然这远不是​​两种语言之间的唯一区别,但这个永远不会令我感到厌倦。

显然,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设计为以下内容:

“当你从一间公寓搬到另一间公寓时,有一个关于搬家的原则,一个有趣的实验是收拾你的公寓,把所有东西都放进箱子里,然后搬进下一间公寓,在你需要的时候不要打开任何东西,重新做你的第一顿饭,然后你从盒子里拿出东西,然后在一个月左右的时间里,你已经用它来很清楚你生活中真正需要什么东西,然后把剩下的东西 - 忘记你有多喜欢它,或者它有多酷 - 然后你就把它扔掉了,这简直令人惊讶,它简化了你的生活,你可以在各种设计问题中使用这个原则:不要仅仅因为它们很酷,或者只是因为它们很有趣。“

你可以在这里阅读引用的上下文

基本上,运算符重载对模拟某种点,货币或复数的类非常有用。 但在此之后,您将很快开始耗尽示例。

另一个因素是开发人员滥用C ++中的功能,如'&&','||',演员操作员和当然'新'等。 Exceptional C ++书中详细介绍了将此与传值和异常相结合导致的复杂性。

链接地址: http://www.djcxy.com/p/27723.html

上一篇: Why doesn't Java offer operator overloading?

下一篇: Failed to retrieve ArrayList from a separate class