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

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

不是Complex a, b, c; a = b + c;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);

是否存在已知的原因,有效的论据, 容许运算符重载? 原因是任意的,还是迷失了时间?


#1楼

说运算符重载会导致逻辑错误,其类型是运算符与操作逻辑不匹配,这就像什么也没说。 如果函数名称不适合操作逻辑,则会发生相同类型的错误-那么解决方案是:放弃函数使用能力! 这是一个可笑的答案-“不适用于运算逻辑”,每个参数名称,每个类,函数或任何在逻辑上不适当的东西。 我认为该选项应该在受人尊敬的编程语言中可用,而那些认为这是不安全的人-嘿,没有人说您必须使用它。 让我们拿C#。 他们下垂了指针,但嘿-有“不安全的代码”声明-根据您的意愿自行编程,后果自负。


#2楼

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

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

代码混淆?

这种说法是谬论。

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

通过函数/方法对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 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的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中,没有指针算术,但是仍然有人发现没有+运算符的字符串连接会很荒谬,无法证明“运算符重载是邪恶的”异常。

, see below) coder, you can't provide your own. 只是您作为C(出于历史原因)或Java(出于 ,请参见下文)编码器,无法提供自己的编码器。

在C ++中,运算符重载不是可选的...

types can have operator overloads. 在C ++中,无法对内置类型进行运算符重载(这是一件好事),但是类型可以具有运算符重载。

如前所述,在C ++中(与Java相反),与内置类型相比,用户类型不被视为该语言的二等公民。 因此,如果内置类型具有运算符,则用户类型也应具有它们。

), C++ operator overloading is so much part of C++ that it becomes as natural as the original C operators, or the before mentioned Java methods. 事实是,就像toString()clone()equals()方法适用于Java( ,C ++运算符重载在C ++中是如此重要,以至于它变得与原始C运算符一样自然,或前面提到的Java方法。

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

...但不应滥用

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

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

所以...好吧...那为什么在Java中不可能呢?

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

我没有将运算符重载作为个人选择,因为我看到太多人在C ++中滥用它。

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

很多C ++设计决策都源于我不喜欢强迫人们以某种​​特定方式做事。[...]通常,我很想宣布自己个人不喜欢的功能为非法,因此我避免这样做,因为我不认为自己拥有将我的观点强加给他人的权利 。

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

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

在C ++中,由于Stroustrup的谦逊,您可以从中受益。 . 在Java中,由于Gosling的 ,您只会被搞砸。

可以将其添加到Java吗?

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

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

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

是的

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

, got it right at first try. 显然,C#人员以 ,在第一次尝试时就正确了。

他们用其他语言来做!

尽管所有针对使用已定义的运算符重载的FUD,以下语言都支持它: ScalaDartPythonF#C#DAlgol 68SmalltalkGroovyPerl 6 ,C ++, RubyHaskellMATLABEiffelLuaClojureFortran 90SwiftAdaDelphi 2005 ...

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

令人回味的食物...


#3楼

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


#4楼

这不是一个不允许这样做的好理由,而是一个实际的理由:

人们并不总是负责任地使用它。 在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 |>>

这里是解释:

/运算符已用作两层之间的合成运算符。 这样做时,下层可以根据上层重载一个或多个默认字段。 (您仍然可以提供所需的值)。 字符串可用作原始层。


#5楼

Java运算符重载的本机支持的替代方法

由于Java没有运算符重载,因此您可以考虑以下几种选择:

  1. 使用其他语言。 GroovyScala都具有运算符重载,并且均基于Java。
  2. 使用java-oo ,这是一个可以在Java中实现运算符重载的插件。 请注意,它不是平台无关的。 此外,它还有很多问题,并且与Java的最新版本(即Java 10)不兼容。 ( 原始StackOverflow源
  3. 使用JNI ,Java本机接口或替代方法。 这使您可以编写用于Java的C或C ++(也许是其他?)方法。 当然,这也不是平台无关的。

如果有人知道其他人,请发表评论,我会将其添加到此列表中。


#6楼

好吧,您真的可以在操作员超载的情况下踩死自己。 就像指针使人们犯了愚蠢的错误,因此决定将剪刀剪掉。

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


#7楼

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


#8楼

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

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


#9楼

假设将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); 

#10楼

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


#11楼

假设你想覆盖由引用的对象的前值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=执行引用复制,因此ab现在引用相同的值。 结果,比较将产生“等于”,因为对象将比较等于自身。

复制和引用之间的差异只会增加操作员重载的混乱。 正如@Sebastian提到的那样,Java和C#都必须分别处理值和引用相等性-operator operator+可能会处理值和对象,但是已经实现了operator=来处理引用。

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


#12楼

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

“有一个关于搬家的原则,当您从一间公寓搬到另一间公寓时,一个有趣的实验是将您的公寓收拾好,将所有物品放入盒子中,然后搬入下一间公寓,直到需要时才拆开任何东西。因此,重新做一顿饭,然后从盒子里拿出东西,然后一个月左右,您就用它来弄清楚自己生活中到底需要什么,然后剩下的就拿走了。东西-忘记了您喜欢它的多少或它有多酷-然后就把它扔掉了,这真是令人惊讶,它简化了您的生活,您可以在各种设计问题中使用该原理:不要仅仅因为它们而做事“很酷,或者只是因为他们很有趣。”

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

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

另一个因素是开发人员滥用了C ++中的功能,从而重载了诸如&& 、、 ||,强制转换运算符,当然还有“ new”之类的运算符。 Exceptional C ++很好地介绍了将其与按值传递和异常相结合而导致的复杂性。


#13楼

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

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

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

#14楼

从技术上讲,每种编程语言中都有运算符重载,可以处理不同类型的数字,例如整数和实数。 说明:重载一词意味着一个功能只有几种实现。 在大多数编程语言中,为+运算符提供了不同的实现,一个为整数,一个为实数,这称为运算符重载。

现在,许多人感到奇怪的是,Java具有操作符+来将字符串加在一起的操作符重载,并且从数学的角度来看,这确实很奇怪,但是从编程语言开发人员的角度来看,添加内置的操作符重载没有任何问题。对于运算符+对于其他类,例如String。 但是,大多数人都同意,一旦您为String添加+的内置重载,那么通常也为开发人员提供此功能是个好主意。

完全不同意运算符重载会混淆代码的谬误,因为这留给开发人员来决定。 想想是天真,老实说,它已经老了。

+1用于在Java 8中添加运算符重载。


#15楼

查看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”,这是正确的。

发布了0 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/asdfgh0077/article/details/104273369
今日推荐