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

时间:2020-03-05 18:57:20  来源:igfitidea点击:

从C ++到Java,一个显而易见的未解决问题是Java为什么不包括运算符重载?

不是复杂的a,b,c吗? a = b + c;比复杂的a,b,c简单得多; a = b.add(c);`?

是否有一个已知的原因,不允许操作符重载的有效参数?原因是任意的,还是迷失了时间?

解决方案

回答

好吧,我们真的可以操作员超负荷射击自己。就像指针使人犯了愚蠢的错误一样,因此决定将剪刀剪掉。

至少我认为这是原因。
无论如何我都站在你这边。 :)

回答

我认为这可能是一个有意识的设计选择,它迫使开发人员创建名称明确传达其意图的功能。在C ++中,开发人员将使运算符过载,而这些功能通常与给定运算符的普遍接受的性质无关,从而使得几乎不可能在不查看运算符定义的情况下确定一段代码的功能。

回答

Java设计人员认为,运算符重载比它值得的麻烦更多。就那么简单。

在每个对象变量实际上都是引用的语言中,运算符重载至少会给C ++程序员带来不合逻辑的额外危险。将情况与C#的==运算符重载以及" Object.Equals"和" Object.ReferenceEquals"(或者任何被称为)进行比较。

回答

假设将Java作为实现语言,则a,b和c都将是对Complex类型的引用,其初始值为null。还要假设Complex是不可变的,就像提到的BigInteger和类似的不可变的BigDecimal一样,我想意思是以下内容,因为我们是将引用分配给从添加b和c返回的Complex,而不是将此引用与a比较。

Isn't :

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

  
  much simpler than:

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

回答

Groovy具有运算符重载,并在JVM中运行。如果我们不介意性能下降(每天变小)。它是根据方法名称自动生成的。例如," +"调用"加号(参数)"方法。

回答

假设我们想覆盖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和Cboth必须分别处理值和引用相等性-" operator +"可能会处理值和对象,但是已经实现了" operator ="来处理引用。

在C ++中,我们一次只能处理一种比较,因此可以减少混乱。例如,在"复杂"上," operator ="和" operator =="都在处理值-分别复制值和比较值。

回答

James Gosling将Java的设计比作以下内容:

"There's this principle about moving, when you move from one apartment to another apartment. An interesting experiment is to pack up your apartment and put everything in boxes, then move into the next apartment and not unpack anything until you need it. So you're making your first meal, and you're pulling something out of a box. Then after a month or so you've used that to pretty much figure out what things in your life you actually need, and then you take the rest of the stuff -- forget how much you like it or how cool it is -- and you just throw it away. It's amazing how that simplifies your life, and you can use that principle in all kinds of design issues: not do things just because they're cool or just because they're interesting."

我们可以在此处阅读报价的上下文

基本上,运算符重载对于建模某种点,货币或者复数的类非常有用。但是之后,我们很快就会用尽示例。

另一个因素是开发人员滥用了C ++中的功能,从而使运算符(如&&& 、、 ||,cast运算符,当然还有" new")过载。 Exceptional C ++本书很好地介绍了将其与按值传递和异常相结合而导致的复杂性。

回答

有时,最好有运算符重载,朋友类和多重继承。

但是我仍然认为这是一个不错的决定。如果Java会有运算符重载,那么如果不查看源代码,我们将永远无法确定运算符的含义。目前没有必要。而且我认为我们使用方法代替运算符重载的示例也很容易理解。如果我们想使事情更清楚,我们可以随时在有毛的声明上方添加注释。

// a = b + c
Complex a, b, c; a = b.add(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",这是正确的。

回答

有很多帖子抱怨操作员超载。

我觉得我必须澄清"操作员超载"的概念,对此概念提供另一种观点。

这种说法是谬论。

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

通过函数/方法对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函数和一个可克隆的实现方法,以及一个+增量运算符。

现在我们知道甚至可以通过原始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

并且这不限于矩阵。 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中没有接口),标准对象(可能没有正确的接口)和用户定义的对象。

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

// 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相反),与内置类型相比,用户类型不被视为该语言的二等公民。因此,如果内置类型具有运算符,则用户类型也应具有它们。

事实是,就像Java的toString(),clone(),equals()方法一样(即类似准标准的方法),C ++运算符重载在C ++中占了很大一部分,以至于自然地作为原始C运算符或者前面提到的Java方法。

结合模板编程,操作员重载已成为众所周知的设计模式。实际上,如果不使用重载运算符,并且在我们自己的类中重载运算符,就无法在STL中走得太远。

...但不应滥用

运算符重载应努力尊重运算符的语义。请勿使用+运算符进行减法(如"请勿使用add函数进行减法"或者"采用克隆方法返回废话"中所述)。

转换过载可能非常危险,因为它们可能导致歧义。因此,它们应该真正保留给定义明确的案例。至于&&||,除非我们真的知道自己在做什么,否则不要重载它们,否则我们将失去本机运算符&&||享有的短路评估。

因为詹姆斯·高斯林(James Gosling)这样说:

I left out operator overloading as a fairly personal choice because I had seen too many people abuse it in C++.
  
  James Gosling. Source: http://www.gotw.ca/publications/c_family_interview.htm

请比较上面的高斯林的文字和下面的Stroustrup的文字:

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.
  
  Bjarne Stroustrup. Source: The Desing and Evolution of C++ (1.3 General Background)

操作符重载对Java有好处吗?

某些对象将从运算符重载(诸如BigDecimal,复数,矩阵,容器,迭代器,比较器,解析器等具体或者数字类型)中受益匪浅。

在C ++中,由于Stroustrup的谦逊,我们可以从中受益。在Java中,由于Gosling的个人选择,我们只会被搞砸。

可以将其添加到Java吗?

现在在Java中不添加运算符重载的原因可能是内部政治,对功能的过敏,对开发人员的不信任(我们似乎破坏了Java团队的破坏者...),与以前的JVM的兼容性,是时候写出正确的规范了,等等。

因此,不要屏住呼吸等待此功能...

但是他们在C#中做到了!!!

是的...

虽然这远不是​​两种语言之间的唯一区别,但是这从未使我感到高兴。

显然,Cfolks的"每个原语都是一个"结构",而一个"结构"是从对象派生的",第一次尝试就正确了。

他们用其他语言来做!!!

尽管所有针对使用已定义的运算符重载的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 ...

如此之多的语言,有着如此之多的(有时是相反的)哲学,但是他们都同意这一点。

令人回味的食物...

回答

说运算符重载会导致某种类型的逻辑错误,即运算符与操作逻辑不匹配,这就像什么也没说。如果函数名称不适合操作逻辑,则会发生相同类型的错误,那么解决方案是:降低函数使用能力!
这是一个滑稽的回答,"不适用于运算逻辑",每个参数名称,每个类,函数或者任何在逻辑上不适当的东西。
我认为该选项应该在受人尊敬的编程语言中可用,而那些认为这是不安全的人,嘿,没有人说我们必须使用它。
让我们拿C#。他们下垂了指针,但是嘿,有一个"不安全代码"语句程序,我们需要自己承担风险。

回答

有人说Java中的运算符重载会导致混淆。那些人是否曾经停下来看一些Java代码,做一些基本的数学运算,例如使用BigDecimal将财务价值提高一定百分比? ....这样一种做法的冗长性成为其自身对混淆的证明。具有讽刺意味的是,将运算符重载添加到Java中将使我们能够创建自己的Currency类,这将使此类数学代码既优雅又简单(减少混淆)。