为什么Java不提供运算符重载?代码混淆?到底什么是混淆?讨论运算符重载所以。。。还行。。。那么为什么在Java中不可能呢?

2022-08-31 04:28:35

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

不是比简单多少吗?Complex a, b, c; a = b + c;Complex a, b, c; a = b.add(c);

是否有已知的原因,不允许运算符重载的有效参数?理由是任意的,还是随着时间的流逝而迷失?


答案 1

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

我觉得我必须澄清“操作员重载”概念,为这个概念提供另一种观点。

代码混淆?

这个论点是一个谬论。

混淆在所有语言中都是可能的...

通过函数/方法对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中的可克隆接口

您应该克隆实现此接口的对象。但你可以撒谎。并创建一个不同的对象。实际上,这个接口非常弱,你可以完全返回另一种类型的对象,只是为了好玩:

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

由于接口可以被滥用/混淆,是否应该以与运算符重载相同的理由C++禁止它?Cloneable

我们可以重载类的方法,让它返回一天中的字符串小时。是否也应该禁止过载?我们可以破坏它返回一个随机值,修改操作数......等等等等。toString()MyComplexNumbertoString()MyComplexNumber.equals

在Java中,就像在C++或任何语言中一样,程序员在编写代码时必须尊重最少的语义。这意味着实现一个 add 函数来 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++中,由于操作员过载,每个容器都使用相同的方式访问其内容。

自然高级类型操作

下面的示例使用一个对象,该对象使用在Google上找到的“Java Matrix对象”和“C++ Matrix对象”的第一个链接找到: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 的 和 类具有相同的令人困惑的详细程度,而它们在C++中的等效项与内置类型一样清晰。BigIntegerBigDecimal

自然迭代器:

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

事实是,就像 , 一样,方法适用于 Java(即类似准标准),C++运算符重载是C++的很大一部分,以至于它变得像原始的 C 运算符或前面提到的 Java 方法一样自然。toString()clone()equals()

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

...但它不应该被滥用

运算符重载应努力尊重运算符的语义。不要在运算符中减去(如“不要在函数中减去”或“在方法中返回废话”)。+addclone

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

所以。。。还行。。。那么为什么在Java中不可能呢?

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

我省略了运算符重载作为一个相当个人的选择,因为我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#的人,用他们的“每个基元都是一个结构,一个结构派生自Object”,在第一次尝试时就得到了正确的结果。

他们用其他语言做到这一点!!!

尽管所有FUD都反对使用定义的运算符重载,但以下语言支持它:KotlinScalaDartPythonF#C#DAlgol 68SmalltalkGroovyRaku(以前称为Perl 6),C++,RubyHaskellMATLABEiffelLuaClojureFortran 90Swift艾达德尔福 2005...

如此多的语言,有如此多的不同(有时是相反的)哲学,但它们在这一点上都同意这一点。

发人深思的东西。。。


答案 2

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

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

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

基本上,运算符重载对于对某种点、货币或复数进行建模的类非常有用。但在那之后,你开始快速用完例子。

另一个因素是开发人员滥用C++中的功能,重载了诸如“&&”,“||”,强制转换运算符,当然还有“新”之类的运算符。将此与传递值和异常相结合所产生的复杂性在特殊C++书中得到了很好的介绍。


推荐