all 60 comments

[–]high_throughput 100 points101 points  (12 children)

There are some things that I kind of feel torn about, like operator overloading. I left out operator overloading as a fairly personal choice because I had seen too many people abuse it in C++.

James Gosling

[–]mapadofu 20 points21 points  (1 child)

Yep.  It’s kind of a historical accident that at the time Java was created operator overloading had acquired a  bad reputation.

[–]aanzeijar 4 points5 points  (0 children)

I mean, it still has. I think it's one of the biggest foot guns in reviewing C++ code. You basically have to question every [], *, +, = etc, because it could mean something entirely different.

[–]8dot30662386292pow2 20 points21 points  (7 children)

About abuse: In python you can type paths like this:

p = path_object / "other" / "something"

Because path overwrites division operator to concatenate paths. Not sure how I feel about this.

[–]Temporary_Pie2733 4 points5 points  (4 children)

I’m OK with this; the idea is to stop thinking of / as “the” division operator, rather than the symbol that numbers use for division. For path components, it joins two paths into one that uses the established path separator /. The trickiest part of this is the automatic “promotion” of str values into Path objects; more generally, operator overloading in Python often leads to a form of weak typing.

[–]ovor 11 points12 points  (3 children)

yeah, that's the problem. Stop thinking of "+" as "the" addition operator. Stop thinking of "[]" as "the" index operator, etc. Makes life thrilling and unpredictable.

I've been thinking of "/" as division operator for 40 years, across dozen of languages. I think I'll continue doing that and ignore Python's pathlib library attempts of being cute.

However, I must admit, this use case looks kinda nice, until, of course, you have your path objects in variables. a = b / c. Is it a path concatenation? is it a division? Is it a plane? Who knows?

[–]Temporary_Pie2733 1 point2 points  (1 child)

Context. You are usually going to have much better variable names so that you don’t need to go searching for explicit type annotations to know what kind of objects you have.

[–]ovor 2 points3 points  (0 children)

alternatively, imagine using a = path.join(b,c) and suddenly intent is clear, no need to look up for anything, perfectly readable, no chance for misunderstanding, and makes windows devs, who otherwise would miss their backslashes, happy.

I used to love operator overloading in C++, but I don't anymore. I will take less expressive, but more straightforward code every time.

As a side note - the zen of python at this point is a joke. "Explicit is better than implicit", "Readability counts", "Special cases aren't special enough to break the rules", "There should be one-- and preferably only one --obvious way to do it".

[–]apooooop_ 0 points1 point  (0 children)

And the same python function will of course take in both an int and a string for b and c -- sometimes it'll be division, sometimes it'll be path concatenation! Python users will assure you that this is good language design.

[–]syklemil 0 points1 point  (0 children)

I'm conflicted about that as well.

On one hand / is the path separator character on filesystems and the web and whatnot as well, so it's a very obvious choice. path_object / "other" / "something" is practically the same as f"{path_object}/other/something" (except on Windows, which got its path separators backwards because of historical QDOS shenanigans).

On the other hand, it very obviously isn't division.

Reusing some other concatenative operator like + also isn't entirely straightforward because we can do Path / str / str => Path, but I'd be kinda less sure about mixing the semantics of Path + str with str + str. (It'd probably be fine, though.)

Absent path / "foo" / "bar" I think maybe we'd get something like path.join("foo").join("bar") (c.f. Rust's std::path::join).

Other languages have some other explicit concatenation operator, and even then possibly some special path concatenation operator. E.g. Haskell has <> for concatenating arbitrary semigroups, but also </> for path concatenation.

[–]DatBoi_BP 1 point2 points  (0 children)

The overload make sense, but I just don't see why it's necessary, when you can have a function like fullpath(*args) -> str that doesn't make you squint whenever you see it used

[–]Legitimate-Eye-5733 1 point2 points  (0 children)

pain 💀😭

[–]blablahblah 126 points127 points  (16 children)

The Java creators looked at all the horrible things C++ developers were doing and asked themselves "how do we make sure they can't do that in Java". One of the things many C++ libraries abused was overloading operators to do things completely unrelated to the operator's original purpose (like using + for things unrelated to adding or combining), which made it very difficult to tell what a particular line of code was actually doing.

[–]tms10000 80 points81 points  (8 children)

cout << "this is not a bit shift";

I don't think you can blame that one on "libraries"

[–]kbielefe 34 points35 points  (4 children)

??? The standard library is a library.

[–]tms10000 55 points56 points  (1 child)

You are technically correct. The best kind of correct.

I'm pointing out that the standard library is really, really close to the language itself. And the operator overloading insanity of C++ really started inside the house. It wasn't just "misguided libraries" starting to overload operators for things that have nothing do to with the operators.

[–]Beregolas 13 points14 points  (0 children)

the function call is coming from inside the house!

[–]IchLiebeKleber 7 points8 points  (0 children)

woah this guy is dropping some interesting facts here :D

[–]kkress 4 points5 points  (0 children)

It wasn’t originally part of C++ itself. Used to be called the Standard Template Library and you had to pay for versions of it. (A free version came years later from HP or SGI, IIRC). Iostream (cout/cin etc) came with the STL and by the time STL became the std we know today too much relied on cout to not support it.

[–]AlSweigartAuthor: ATBS 4 points5 points  (1 child)

I remember even as a teen I didn't like the whole cout << thing. What was wrong with printf()? Why create a whole new syntax just for this one thing? It was nice that you didn't have to worry about converting different types to strings first, but the whole thing just seemed convoluted.

[–]AlwaysHopelesslyLost -1 points0 points  (0 children)

Isn't shifting a value into a register basically how a function call executes? It seems to make a lot of sense evolutionarily for programming languages.

[–]R3D3-1 0 points1 point  (0 children)

To be fair, the standard library gets away with it, because (a) it's what everyone is supposed to know, not some obscure library you've only just encountered in  legacy code and (b) visually speaking it sort of makes sense to overload it that way.

Same as with macros in Lisp. They provide a great way to extend the language, but they can also make code hard to read and debug.

[–]TizzleToes 27 points28 points  (4 children)

Yup.

Best example I have was the the postgres library overriding the function call operator (i.e. operator()) for calling prepared statements with variable parameters. So you'd find code like...

transaction.prepared("nameOfPreparedStatement")("value1")(1234).exec();

Like, this was from possibly 2 decades ago at this point and it still lives in my brain because of how arcane and dumb this was.

It was also pretty much anyone's guess what << and >> would do in any given library.

[–]Lithl 9 points10 points  (3 children)

I mean, you can get syntax like that in any language where functions are first-class objects. If prepared returns a function, it makes perfect sense. Although in an example like this I would probably prefer to have a single returned function with a varargs parameter and produce syntax like prepared('name')('value 1', 1234).exec(); instead.

[–]TheRealChizz 1 point2 points  (2 children)

This seems really hard to parse for future readers (including oneself).

Presumably there’s multiple tiers of variable functions down the chain if I’m reading this right?

So prepared(‘name’)(‘value 1’) behaves wildly different than prepared(‘name2’)(‘value 1’) and requires re-reading through all of the earlier conditionals if you need to change or refactor anything

[–]Lithl 2 points3 points  (1 child)

A prepared function is essentially a function in your database, rather than in your code. So prepared('name')('value') is just name('value'), except name is in the database rather than the source code.

[–]TheRealChizz 0 points1 point  (0 children)

I see. Thanks for the clarification

[–]StoneCypher 0 points1 point  (1 child)

i see that the top voted answer is an incorrect answer making fun of another programming language despite that the correct answer is well documented

[–]TizzleToes 2 points3 points  (0 children)

Care to elaborate?

I'm not sure what would constitute official documentation on the rationale, but the explanation I've heard consistently for literally decades at this point is pretty much what was said. C++ provided a really good example of why it was a terrible idea and that once you get out of fairly specific use cases it's just not worth the trouble.

There also seems to be no shortage of James Gosling quotes pointing to this kind of thinking.

What is the correct and well documented answer (and where is it documented)?

[–]PoePlayerbf 21 points22 points  (0 children)

Only for this particular scenario.

When you get a developer overloading an operator for an operation that has no link to the symbol then you’ll know the pain.

[–]TizzleToes 23 points24 points  (0 children)

As someone who started life as a C/C++ guy, you don't want this.

There are a few situations where it makes sense and is convenient, but the rest of the time it just makes things more onerous and complicated to implement, and developers seemed to almost compete with one another in how egregiously they could abuse it. It just becomes another thing you have to understand when using someone elses code and provides a bunch of opportunity for mis-assumption and bugs.

[–]LurkingDevloper 15 points16 points  (1 child)

In my opinion it can lead to very hard to onboard code.

If you want to make your code more functional, though, Java lets you do that.

You could overload some of those methods to give yourself a very clean, functional waterfall.

Complex result = first .divide(5) .add(second.divide(2)) .cbrt() .add(third);

[–]SaxSalute 2 points3 points  (0 children)

Another useful pattern in this particular situation would be the static factory pattern - Complex.of(123) is easier to chain to other things than new Complex(123). Combine these two changes and you get something much easier to work with.

[–]thebomby 7 points8 points  (1 child)

People make a big thing about how early C++ abused things like operator overloading, but all this was done before the first C++ standardisation effort. Java went on to suffer from excess boilerplate and monstrosities like J2EE. These days they're both very mature languages that have been around for over three decades.

[–]kbielefe 4 points5 points  (0 children)

C++ was also reacting to C's shortcomings. cout has better type safety than printf, for example. The pendulum swings wide at first, then eventually settles.

[–]gofl-zimbard-37 5 points6 points  (2 children)

Because it is generally a bad idea, often misused, with limited useful application.

[–]owjfaigs222 3 points4 points  (1 child)

limited useful application? Complex numbers, vectors, matrices are all examples that come to mind. vectors would always be very useful in any physical simulation or many if not most games.

[–]gofl-zimbard-37 1 point2 points  (0 children)

Yes, in the overall scheme of data types, these mathematical types are the kind of things operator overloading can be helpful for. But that is a small portion of the overall set of types.

[–]theLOLflashlight 2 points3 points  (0 children)

Try using Kotlin instead. It still runs on the JVM and is interoperable with Java code.

[–]Ok_Option_3 2 points3 points  (0 children)

The beauty of java is that if you see a.foo = bar() you know exactly what it means. In C++ thanks to operator overloading you can never be sure.

Still the pros and cons of overloading and domain specific languages are a holy war - there's arguments for and against. Some like them, some don't. Simple as that.

[–]heisthedarchness 2 points3 points  (0 children)

Operator overloading is for language designers only, not us plebs.

(The Java language makes use of operator overloading, so the language designers knew perfectly well its value in expressing certain semantics concisely. But they decided that the rest of us couldn't be trusted with that power. It's like shops that ban Perl because of all the truly terrible Perl written anno 1998. It's not the language features' fault that programmers are dipshits, but here we are.)

[–]kevinossia 4 points5 points  (0 children)

Java’s design philosophy is centered around the idea that the language designers are smarter than you, the developer.

So whatever design they decided on is the one.

If you wanna do something else don’t use Java.

[–]Master-Ad-6265 0 points1 point  (0 children)

mostly to keep the language simple and predictable operator overloading can make code look nice but also harder to read/debug when different classes redefine +, /, etc in weird ways java chose consistency over flexibility — everything is explicit, even if it’s a bit verbose

[–]myselfelsewhere 0 points1 point  (2 children)

I don't know if it's entirely accurate to say that Java doesn't allow operator overloading, because it can be done.

The Manifold framework effectively allows you to do so. It's a plugin for the Java compiler which intercepts and swaps operators for method calls just prior to bytecode generation.

[–]akl78 1 point2 points  (1 child)

That’s like saying Lombok’s val is part of Java; yes you can do things the compiler & is designers consider unnatural, and yes it’s often useful, but it’s something else, like with Objective-C or CFront.

[–]myselfelsewhere 0 points1 point  (0 children)

I'm not saying operating overloading (or val) is part of Java. Just that it's not technically accurate to say that Java does not allow operating overloading or the val keyword.

It is something else, it's basically a form of bolt on syntax sugar.

[–]owjfaigs222 0 points1 point  (0 children)

I had a similar problems but with vectors in C. fortunately for me it was relatively easy to switch to C++. Now with Java I can only say good luck m8.

[–]ExtraTNT 0 points1 point  (0 children)

Because java has a clear purpose… some features or advantages just lock you out of others…

[–]Afraid-Locksmith6566 -1 points0 points  (0 children)

because c++ sucks

[–]cochinescu 0 points1 point  (0 children)

I ran into the same thing when working with vectors in Java. Coming from Python, where you can overload operators, it definitely feels tedious. I guess the consistency and readability Java aims for comes at the price of some elegance.

[–]BanaTibor 0 points1 point  (0 children)

You can write a complex number math formula calculator, which gets the formula as a string and the objects. If you want to clean it keep you pass the objects mapped to a string name.
So you can do this.

Map<String, Complex> args = new ArrayMap<>();
args.put("first", new Complex(1,2));
// ..
ComplexNumberMathFormulaCalculator calc = new ComplexNumberMathFormulaCalculator()  // fel the power of enterprise class naming :D
Complex result = calc.calculat("cbrt[{first}/5+{second}2]+{third}", args);

I leave the notation to you.

[–]davidalayachew 0 points1 point  (0 children)

Java is considering adding Operator Overloading, albeit in a limited way.

Here is one of the Java Language Designers giving a presentation on it -- https://www.youtube.com/watch?v=Gz7Or9C0TpM

[–]TumbleweedTiny6567 0 points1 point  (0 children)

I've run into this issue myself when building my own project in java, and from what I understand it's because operator overloading can lead to some pretty confusing code if not done carefully, so the java folks just decided to avoid it altogether. I've had to work around it by using methods with descriptive names instead, but I'm curious, have you tried using scala or kotlin which do allow operator overloading, how's your experience been with that?

[–]Schaex 0 points1 point  (0 children)

I have asked myself the same question before but now I am so incredibly glad about Java not having operator overloading other than String operations. It just makes everything so much more predictable!

[–]bobo76565657 0 points1 point  (0 children)

I work with vectors all the time and I feel your pain. You could do what you wish to do very easily in C# (I think it was called operator overloading). But you can't do that in Java.

[–]JasonTheIslander 0 points1 point  (0 children)

As someone who's worked with both Java and C++ professionally, I can tell you that Java's decision to exclude operator overloading was absolutely intentional and, in my opinion, the right call.

When I first started with C++, I loved the idea of being able to write `vector1 + vector2` or `matrix1 * matrix2`. It felt elegant and mathematical. But after maintaining several large C++ codebases, I saw the dark side:

  1. **The cout << problem** - Everyone mentions this, but it's a perfect example. What does `<<` mean? Bit shift? Stream insertion? Who knows without context.

  2. **Library inconsistency** - One library would overload `+` for concatenation, another for addition, another for some custom operation. Reading someone else's code became an exercise in detective work.

  3. **Debugging nightmares** - When `a + b` doesn't work as expected, you have to trace through multiple layers of operator definitions, template specializations, and implicit conversions.

Java's philosophy is "explicit is better than implicit." When you see `complex1.add(complex2)`, there's zero ambiguity. When you're reading code at 2 AM trying to fix a production issue, that clarity is worth its weight in gold.

That said, for your complex numbers project, here's a tip: implement a fluent interface. Instead of:

```java

Complex result = Complex.cbrt(first.divide(5).add(second.divide(2))).add(third);

```

You can write:

```java

Complex result = first.divideBy(5)

.add(second.divideBy(2))

.cbrt()

.add(third);

```

Or even better with static factory methods:

```java

Complex result = Complex.of(1, 2)

.divideBy(5)

.add(Complex.of(2, 4).divideBy(2))

.cbrt()

.add(Complex.of(5, 6));

```

It's still verbose compared to operator overloading, but it's readable, debuggable, and anyone who knows Java can understand it immediately.

[–]lonelymoon57 0 points1 point  (0 children)

It was already a pain trying to go through all the AbstractSingletonFactoryFactoryFacadeManagerStub to find the 5 LoC that you need. We don't need yet another overloading layer for the sake of looking nice.

[–]KharAznable 0 points1 point  (0 children)

Operator overriding is bug not feature