从C++到Java,显然没有答案的问题是为什么Java没有包含操作符重载?
Complex a, b, c; a = b + c;不是比Complex a, b, c; a = b.add(c);简单多了吗?
是否有一个已知的原因,不允许运算符重载的有效参数?原因是武断的还是浪费时间的?
有很多帖子抱怨操作符过载。好的。
我觉得我必须澄清"操作符重载"的概念,提供关于这个概念的另一种观点。好的。代码模糊?
这个论点是错误的。好的。在所有语言中都有可能混淆…
在C或Java中通过函数/方法混淆代码很容易,因为C++是通过运算符重载来实现的:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // 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中的EDOCX1 0接口。好的。
您应该克隆实现此接口的对象。但你可以撒谎。并创建另一个对象。事实上,这个接口太弱了,你完全可以返回另一种类型的对象,只是为了好玩:好的。
1 2 3 4 5 6 7
| class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
} |
当EDCOX1 0接口可以被滥用/混淆时,它是否应该被禁止在相同的理由C++运算符重载应该是什么?好的。
我们可以重载MyComplexNumber类的toString()方法,让它返回一天中的字符串化时间。是否也应该禁止toString()过载?我们可以破坏MyComplexNumber.equals,让它返回一个随机值,修改操作数…等。好的。
在Java中,与C++一样,或者任何语言,程序员在编写代码时必须遵守最小的语义。这意味着实现一个添加的add函数,和一个克隆的Cloneable实现方法,以及一个比递增的++运算符。好的。到底是什么让人困惑?
既然我们知道代码可以通过原始Java方法被破坏,我们可以问自己C++中运算符重载的真正用法吗?好的。清晰自然的符号:方法与运算符重载?
下面我们将比较不同的情况,爪哇和C++中的"相同"代码,以了解哪种编码风格更清晰。好的。自然比较:
1 2 3 4 5 6 7 8 9 10 11
| // 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不是基元时,即使对于原始对象(BigTigin,等等),代码也会变得非常混乱。好的。自然数组/容器访问器和订阅:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 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++中,由于运算符重载,每个容器都使用相同的方法访问其内容。好的。自然高级类型操作
下面的例子使用EDCOX1的9个对象,用谷歌上找到的第一个链接找到"Java矩阵对象"和"C++矩阵对象":好的。
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 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 |
这不仅限于矩阵。EDCOX1 10和EDCOX1,11个类Java遭受相同的混淆冗长,而它们在C++中的等价物与内置类型一样清晰。好的。自然迭代器:
1 2 3 4 5 6 7 8 9 10 11 12
| // 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 |
自然功能:
1 2 3 4 5
| // C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ; |
文本连接:
1 2 3 4 5 6 7 8 9
| // 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中你也可以使用EDCOX1 0。但是,等等:这是运算符重载,不是吗?这不是作弊吗????好的。
-D好的。通用代码?
修改操作数的通用代码应该既可以用于内置的INS/原语(Java中没有接口),也可以使用标准对象(它没有正确的接口)和用户定义的对象。好的。
例如,计算任意类型的两个值的平均值:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // 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语言中,运算符的实际行为将根据其操作数而改变。例如,添加两个整数不同于添加两个双精度数,甚至一个整数和一个双精度数。甚至还有整个指针算术域(不进行强制转换,您可以向指针添加一个整数,但不能添加两个指针…)。好的。
在爪哇中,没有指针运算,但仍有人发现没有EDCX1〔1〕操作符的字符串连接将是可笑的,足以证明"运算符重载是邪恶"信条中的一个例外。好的。
只是作为一个C(由于历史原因)或Java(出于个人原因,见下文)编码器,你不能提供你自己的。好的。在C++中,操作符重载不是可选的…
在C++中,内置类型的运算符重载是不可能的(并且这是一件好事),但是用户定义的类型可以有用户定义的运算符重载。好的。
正如前面已经说过的,在C++中,与Java相反,与内置类型相比,用户类型不被认为是语言的二等公民。因此,如果内置类型具有运算符,那么用户类型也应该能够拥有它们。好的。
事实上,像EDCOX1的0位,EDCOX1,1,EDCX1,2种方法是Java(即准标准),C++运算符重载是C++的一部分,它与原C运算符一样自然,或者前面提到的Java方法。好的。
结合模板编程,运算符重载成为一种众所周知的设计模式。事实上,在STL中,如果不使用重载运算符,也不为自己的类使用重载运算符,就不能走得很远。好的。…但不应滥用
运算符重载应该努力尊重运算符的语义。不要在+运算符中减去(如"不要在add函数中减去",或"在clone方法中返回垃圾")。好的。
强制转换重载可能非常危险,因为它们会导致模棱两可。因此,它们确实应该为定义明确的案例保留。至于&&和||,除非你真的知道你在做什么,否则永远不要让它们过载,因为你会失去本土运营商&&和||喜欢的短路评估。好的。所以…好啊。。。那么为什么Java不可能呢?
因为詹姆斯·戈斯林说过:好的。
I left out operator overloading as a fairly personal choice because I had seen too many people abuse it in C++.
Ok.
James Gosling. Source: http://www.gotw.ca/publications/c_family_interview.htm
Ok.
请将上面的戈斯林文本与下面的斯特劳斯鲁普文本进行比较:好的。
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.
Ok.
Bjarne Stroustrup. Source: The Desing and Evolution of C++ (1.3 General Background)
Ok.
操作员超载有利于Java吗?
一些对象将从运算符重载(具体或数值类型,如bigdecimal、复数、矩阵、容器、迭代器、比较器、解析器等)中受益匪浅。好的。
在C++中,由于Stroustrup的谦逊,你可以从中受益。在爪哇,因为戈斯林的个人选择,你完全被搞砸了。好的。它可以添加到Java吗?
在Java中不添加操作符重载的原因可能是内部政治、对特性的过敏症、开发人员的不信任(你知道,那些困扰Java团队的破坏者……)、与以前JVM的兼容性、编写正确规范的时间等等。好的。
所以不要屏住呼吸等待这个功能…好的。但他们用C来做!!!!
是啊。。。好的。
虽然这远非这两种语言之间唯一的区别,但这一种语言却总能取悦我。好的。
显然,C_族的"每个原语都是一个struct,一个struct是从物体衍生出来的",他们一开始就做对了。好的。他们用其他语言做的!!!!
尽管所有的FUD都反对使用定义的运算符重载,但以下语言支持:斯卡拉、DART、Python、F*、C、D、Algol 68、SimulalTalk、Groovy、Perl 6、C++、露比、Haskell、Matlab、Effell、Lua、Crojule、Fortran 90、SWIFT、艾达、Delphi 2005…好的。
有那么多语言,有那么多不同的(有时是对立的)哲学,但他们都同意这一点。好的。
思考的食物…好的。好啊。
杰姆斯.高斯林把设计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++在开发人员重载运算符中滥用了特性,比如"& & &,",'`',演员运算符,当然还有"新"。将此与通过值和异常相结合所带来的复杂性在例外C++书籍中得到了很好的覆盖。
查看boost.units:链接文本
它通过运算符重载提供零开销维度分析。这能清楚多少?
1 2 3 4
| quantity<force> F = 2.0*newton;
quantity<length> dx = 2.0*meter;
quantity<energy> E = F * dx;
std::cout <<"Energy =" << E << endl; |
将实际输出"能量=4 J",这是正确的。
假设您想要覆盖a所引用对象的先前值,那么就必须调用一个成员函数。
1 2 3
| Complex a, b, c;
// ...
a = b.add(c); |
在C++中,这个表达式告诉编译器在堆栈上创建三(3)个对象,执行加法,并将临时对象中的所得值复制到现有对象EDCOX1×2中。
然而,在Java中,EDCOX1(4)不为引用类型执行值复制,用户只能创建新的引用类型,而不是值类型。因此,对于名为Complex的用户定义类型,赋值意味着复制对现有值的引用。
请考虑:
1 2 3 4
| b.set(1, 0); // initialize to real number '1'
a = b;
b.set(2, 0);
assert( !a.equals(b) ); // this assertion will fail |
在C++中,复制值,因此比较结果不相等。在爪哇中,operator=执行参考拷贝,因此a和EDCX1〔8〕现在指的是相同的值。因此,比较将生成"equal",因为对象将与自身进行比较。
副本和引用之间的差异只会增加运算符重载的混乱。正如塞巴斯蒂安所提到的,Java和C语言都必须分别处理值和引用等式——EDCOX1×9可能会处理值和对象,但是已经实现了EDCOX1×4 }来处理引用。
在C++中,你应该每次只处理一种比较,这样它就不会那么混乱了。例如,在Complex上,operator=和operator==都在处理值——分别复制值和比较值。
Java设计者认为操作员重载是比它更值得的麻烦。很简单。
在一个语言中,每个对象变量实际上是一个引用,运算符重载至少会带来一个不合逻辑的额外危险——至少是一个C++程序员。将这种情况与c s==运算符重载和Object.Equals和Object.ReferenceEquals进行比较(或与它的调用进行比较)。
groovy具有运算符重载,并在JVM中运行。如果你不介意性能冲击(每天都会变小)。它是基于方法名自动生成的。例如,'+'调用'plus(argument)'方法。
我认为这可能是一个有意识的设计选择,迫使开发人员创建名称清楚地传达其意图的函数。在C++开发人员中,操作符的功能往往与给定操作符的普遍接受性质无关,这使得几乎不可能在不查看操作符的定义的情况下确定代码是什么。
好吧,你真的可以在操作人员过载的情况下把自己踩到脚上。就像用指针一样,人们会犯愚蠢的错误,所以决定把剪刀拿走。
至少我认为这就是原因。不管怎样,我站在你这边。:)
从技术上讲,每种编程语言中都存在运算符重载,可以处理不同类型的数字,例如整数和实数。说明:术语重载意味着一个函数只有几个实现。在大多数编程语言中,运算符+提供了不同的实现,一个用于整数,一个用于reals,这称为运算符重载。
现在,许多人发现Java有操作符重载给操作符+添加字符串是很奇怪的,从数学的观点来看,这确实很奇怪,但是从编程语言的开发者的角度来看,对于操作员+为其他类添加Buffin运算符重载没有什么错。字符串。但是,大多数人都同意,一旦您为字符串添加了内置的+重载,那么通常最好也为开发人员提供此功能。
A完全不同意这样一种谬论,即运算符重载会混淆代码,这是留给开发人员决定的。这是Na?我得想想,老实说,它变老了。
+ 1,用于在Java 8中添加运算符重载。
有人说,Java中的运算符重载会导致混淆。那些人是否曾经停下来查看一些Java代码,做一些基本的数学运算,比如用BigDimple增加百分比的财务价值?…这样一个练习的冗长内容成为它自己的陈词滥调的证明。具有讽刺意味的是,向Java添加运算符重载将允许我们创建自己的货币类,这样可以使这种数学代码优雅而简单(不太模糊)。
如果说运算符重载会导致类型为该运算符与操作逻辑不匹配的逻辑错误,这就像什么都不说一样。如果函数名不适合操作逻辑,也会出现同样类型的错误-那么解决方法是:放弃函数使用的能力!?这是一个滑稽的回答——"不适合操作逻辑",每个参数名、每个类、函数或任何逻辑上不合适的东西。我认为这个选项应该可以用受人尊敬的编程语言来使用,而那些认为它不安全的人——嘿,没有人说你必须使用它。让我们用C。它们的指针是下垂的,但嘿-有"不安全的代码"的声明-程序,你喜欢自己的风险。
假设Java是实现语言,那么A、B和C都将是对具有初始值NULL的类型复杂的引用。另外,假设复数是不可变的,如前面提到的biginteger和类似的不可变bigdecimal,我想您的意思是,当您将引用分配给从添加b和c返回的复数,而不是将此引用与a进行比较时。
Isn't :
1
| Complex a, b, c; a = b + c; |
much simpler than:
1
| Complex a, b, c; a = b.add(c); |
有时,最好是让操作符重载、友元类和多个继承。
但我仍然认为这是一个好决定。如果Java会有操作符重载,那么我们就不可能在不查看源代码的情况下确定运算符的含义。目前这是不必要的。我认为您使用方法而不是运算符重载的示例也是非常可读的。如果你想让事情变得更清楚,你可以在多毛的陈述上面添加评论。
1 2
| // a = b + c
Complex a, b, c; a = b.add(c); |
Java运算符重载的本机支持的替代方案
由于Java没有操作符重载,这里有一些可供选择的替代方案:
使用其他语言。Groovy和斯卡拉都有操作符重载,并且都是基于Java的。
使用Java OO,一个可以在Java中实现操作符重载的插件。请注意,它不是平台独立的。此外,它还有许多问题,与Java的最新版本(即Java 10)不兼容。(原始stackoverflow源)
使用JNI、Java本机接口或替代方案。这允许你写C或C++(也许其他的?)在Java中使用的方法。当然,这也不是平台独立的。
如果有人知道其他人,请发表评论,我会将其添加到此列表中。
这不是不允许的好理由,而是一个实际的理由:
人们并不总是负责任地使用它。从python库scapy中看这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| >>> 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
"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0
' |>>>
>>> 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 |>> |
解释如下:
The / operator has been used as a composition operator between two
layers. When doing so, the lower layer can have one or more of its
defaults fields overloaded according to the upper layer. (You still
can give the value you want). A string can be used as a raw layer.