为什么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=
执行引用复制,所以a
和b
现在引用相同的值。 因此,比较会产生“相等”,因为对象将与自身相等。
副本和引用之间的差异只会增加运算符重载的混淆。 正如@Sebastian所说,Java和C#都必须分别处理值和引用的相等性 - operator+
可能会处理值和对象,但operator=
已经实现来处理引用。
在C ++中,你只应该一次处理一种比较,所以它可能不那么令人困惑。 例如,在Complex
, operator=
和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的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相反,与内置类型相比,用户类型不被视为语言的二级公民。 所以,如果内置类型有操作符,用户类型也应该能够拥有它们。
事实是,像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