Coming from C++ to Java, the obvious unanswered question is why didn't Java include operator overloading?
Isn't Complex a, b, c; a = b + c;
much simpler than Complex a, b, c; a = b.add(c);
?
Is there a known reason for this, valid arguments for not allowing operator overloading? Is the reason arbitrary, or lost to time?
There are a lot of posts complaining about operator overloading.
I felt I had to clarify the "operator overloading" concepts, offering an alternative viewpoint on this concept.
This argument is a fallacy.
It is as easy to obfuscate code in C or Java through functions/methods as it is in C++ through operator overloads:
// 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 ;
}
For another example, let's see the Cloneable
interface in Java:
You are supposed to clone the object implementing this interface. But you could lie. And create a different object. In fact, this interface is so weak you could return another type of object altogether, just for the fun of it:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
As the Cloneable
interface can be abused/obfuscated, should it be banned on the same grounds C++ operator overloading is supposed to be?
We could overload the toString()
method of a MyComplexNumber
class to have it return the stringified hour of the day. Should the toString()
overloading be banned, too? We could sabotage MyComplexNumber.equals
to have it return a random value, modify the operands... etc. etc. etc..
In Java, as in C++, or whatever language, the programmer must respect a minimum of semantics when writing code. This means implementing a add
function that adds, and Cloneable
implementation method that clones, and a ++
operator than increments.
Now that we know that code can be sabotaged even through the pristine Java methods, we can ask ourselves about the real use of operator overloading in C++?
We'll compare below, for different cases, the "same" code in Java and C++, to have an idea of which kind of coding style is clearer.
// 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 ;
Please note that A and B could be of any type in C++, as long as the operator overloads are provided. In Java, when A and B are not primitives, the code can become very confusing, even for primitive-like objects (BigInteger, etc.)...
// 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
In Java, we see that for each container to do the same thing (access its content through an index or identifier), we have a different way to do it, which is confusing.
In C++, each container uses the same way to access its content, thanks to operator overloading.
The examples below use a Matrix
object, found using the first links found on Google for "Java Matrix object" and "C++ Matrix object":
// 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
And this is not limited to matrices. The BigInteger
and BigDecimal
classes of Java suffer from the same confusing verbosity, whereas their equivalents in C++ are as clear as built-in types.
// 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") ;
Ok, in Java you can use MyString = "Hello " + 25 + " World" ;
too... But, wait a second: This is operator overloading, isn't it? Isn't it cheating???
:-D
The same generic code modifying operands should be usable both for built-ins/primitives (which have no interfaces in Java), standard objects (which could not have the right interface), and user-defined objects.
For example, calculating the average value of two values of arbitrary types:
// 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.
Now that we have seen fair comparisons between C++ code using operator overloading, and the same code in Java, we can now discuss "operator overloading" as a concept.
Even outside of computer science, there is operator overloading: For example, in mathematics, operators like +
, -
, *
, etc. are overloaded.
Indeed, the signification of +
, -
, *
, etc. changes depending on the types of the operands (numerics, vectors, quantum wave functions, matrices, etc.).
Most of us, as part of our science courses, learned multiple significations for operators, depending on the types of the operands. Did we find them confusing, them?
This is the most important part of operator overloading: Like in mathematics, or in physics, the operation depends on its operands' types.
So, know the type of the operand, and you will know the effect of the operation.
In C, the real behavior of an operator will change according to its operands. For example, adding two integers is different than adding two doubles, or even one integer and one double. There is even the whole pointer arithmetic domain (without casting, you can add to a pointer an integer, but you cannot add two pointers...).
In Java, there is no pointer arithmetic, but someone still found string concatenation without the +
operator would be ridiculous enough to justify an exception in the "operator overloading is evil" creed.
It's just that you, as a C (for historical reasons) or Java (for personal reasons, see below) coder, you can't provide your own.
In C++, operator overloading for built-in types is not possible (and this is a good thing), but user-defined types can have user-defined operator overloads.
As already said earlier, in C++, and to the contrary to Java, user-types are not considered second-class citizens of the language, when compared to built-in types. So, if built-in types have operators, user types should be able to have them, too.
The truth is that, like the toString()
, clone()
, equals()
methods are for Java (i.e. quasi-standard-like), 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.
Combined with template programming, operator overloading becomes a well known design pattern. In fact, you cannot go very far in STL without using overloaded operators, and overloading operators for your own class.
Operator overloading should strive to respect the semantics of the operator. Do not subtract in a +
operator (as in "do not subtract in a add
function", or "return crap in a clone
method").
Cast overloading can be very dangerous because they can lead to ambiguities. So they should really be reserved for well defined cases. As for &&
and ||
, do not ever overload them unless you really know what you're doing, as you'll lose the the short circuit evaluation that the native operators &&
and ||
enjoy.
Because James Gosling said so:
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
Please compare Gosling's text above with Stroustrup's below:
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 Design and Evolution of C++ (1.3 General Background)
Some objects would greatly benefit from operator overloading (concrete or numerical types, like BigDecimal, complex numbers, matrices, containers, iterators, comparators, parsers etc.).
In C++, you can profit from this benefit because of Stroustrup's humility. In Java, you're simply screwed because of Gosling's personal choice.
The reasons for not adding operator overloading now in Java could be a mix of internal politics, allergy to the feature, distrust of developers (you know, the saboteur ones that seem to haunt Java teams...), compatibility with the previous JVMs, time to write a correct specification, etc..
So don't hold your breath waiting for this feature...
Yeah...
While this is far from being the only difference between the two languages, this one never fails to amuse me.
Apparently, the C# folks, with their "every primitive is a struct
, and a struct
derives from Object", got it right at first try.
Despite all the FUD against used defined operator overloading, the following languages support it: 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...
So many languages, with so many different (and sometimes opposing) philosophies, and yet they all agree on that point.
Food for thought...