Occasionally, I like to take a step back and look at the programming languages I've used over the years. What better place to start than with my first, my last, my everything?
I've been programming Java since high school, and it was the first real language I was exposed to. I suspect that, when I sleep, I still type
public static void main(String args)
because it's that ingrained in my muscle memory. And you know what?
Java is an awful choice for a first language.
I'm not going to just state that, I'll give reasons:
- Java requires Object-Oriented Programming. Every java program is a function embedded in an object. Way to keep a gentle learning curve, there.
- Java relies extensively on imports from other libraries, and other classes and types. That's so much to explain to a beginning programmer. Or not, which was the case with me: just don't explain anything, and just use it.
- Java forces exception handling. Never mind that you're just starting out and probably don't even know what an exception is.
- Java is garbage-collected, which means that you don't even know where things are going.
newis just a keyword that makes things happen to you.
- Java tends to use verbosity to the extreme: it makes simple tasks tedious, and complex tasks impossible.
Probably think with Point 4 up there that I advocate C or C++ instead, right? Well, yes and no. I think that those languages are great for teaching a fledgling programmer that the program you write can do horrible, terrifying things if you fuck up, which can be great for traumatizing a freshman programmer into getting his/her shit together, or it can mess up their mind really badly. I also think that languages like Python are too high-level to make certain the programmer knows exactly what they're doing, relying on language constructs more than common sense. So, I'd actually like to see someone teach using C and Python in combination. (Though not in CPython, because that's really just the worst of both worlds.)
Anyway, this article isn't about ripping on Java as a choice of teaching language. It's about ripping on Java's design choices. Full disclosure from this point on: I actually write Java full-time, so I'm going to avoid writing my way out of a job.
Let's get down to it:
- Verbosity. Oh my God the verbosity. My fingers sometimes hurt after typing Java more than they'd hurt after an essay or a particularly long-winded blog post.
- Strictness in file organization. A file named [Foo].java can only have one public class named [Foo]. If it's in a packaged called com.abba.jesus.pidgeot, then it's file path has to be
[something]/com/abba/jesus/pidgeot/Foo.java, even if there's nothing in packages com, com.abba, or com.jesus.
- Weird type-boxing/casting rules. Each of the value types is implicitly convertible to a related class in
java.util.Integer)...except as an argument to a generic class, where it has to be declared as the class rather than the value-type (ie.
List<int>is a ParseException). This is fine (mostly), except that any function that takes a generic
Thas to have a separate implementation for each value type. Further, Java disallows casts between different class types (that aren't covariant)...except for a variable of any type
v + ""is always a string and never results in a NullPointerException when
- The weirdness of exception handling. Prior to Java 7, there was no way to handle multiple exceptions in a single block, leaving copious room for typos in copy-pasting the same damn block to both related handlers. Oh, and you have to handle every Exception, regardless of if it makes sense to...unless it's a RuntimeException. Then the JVM gives it a free pass. Why even have a duality? Why not just have stuff like that be handled as Errors or something?
- Weirdly inconsistent library support. See this article on the concept of a Pair class, for instance. They basically say, "Oh I don't think it's necessary, so no one else will." I've also heard people say, "Programmers will use it wrong." That is a terrible reason not to support something, especially in a util class of all things. On that note...
- Complete lack of operator overloading. Which would you rather see:
BigInteger a = new BigInteger("12343212"); BigInteger b = new BigInteger("234231999"); BigInteger c = new BigInteger("65565790099"); return c.add(b.muliply(a));or
return c + (b * a);Again, the response seems to be, "People will use it wrong." And a lot of the time, the hatred is directed at C++, specifically the stream operator:
int i; cin >> i; cout << i << endl;
To compare, let's discuss Scala, a language built on top of the JVM. Let's see what it does differently:
- Verbosity. Scala tends towards the succinct whenever possible, at least in my experience.
- More free-form package organization. Scala will find your files, don't worry about what you name them. You can also put more than one top-level public class in a single file (though this is still discouraged)
- More intuitive type-coercion. Scala's type system is complex. I don't know enough about lambda calculus to pretend I fully understand every part of it, but I do know they make value types effectively just a different part of the standard hierarchy.
List<int>isn't a syntax problem anymore: it just works.
- Free-form casting and conversions. Want
Ato always make sense as a
B? Why not! You can do that.
- No exceptions are forced to be checked anymore. Rejoice!
- On the library support, it just tries to have as wide support as possible, which isn't always a good thing. (Want to know all of Scala? Too bad, that's what the internet's for.)
- Operator overloading is just another function. This maybe goes a bit too far, since you can make operators using basically any character. (The
/:operator for fold-left is my favourite.)
And, while we're at it, let's look at C#, a language made because Microsoft didn't want to be sued for using Java at one point:
- Still verbose, though I'd argue less so.
- Package organization? Totally up to you. Hey, have 5 classes per file if you want.
- A similar (though simpler) type system to Scala. Value types are
structs, everything else is an
structs can be semantically treated like
- Define your own cast operators! Make them as explicit as you want!
- Exceptions don't need to be checked. Funny how often this pops up...
- A decent-ish standard library which supports most of the things you'd want. Maps that actually act the same as other iterables (as they damn-well should).
- Override only the existing operators and only within the definition of the class itself (ie, no overriding
int + intor something). Humourously, the bit shift operators (ie. the straming operators in C++) can only have the second operand be an
int, because they're still pissed about that.
Don't take this to mean Java does everything wrong. The JVM is a great piece of technology, and the language has some neat constructs seen rarely elsewhere. (Anonymous interfaces, anyone?) But so many of its design decisions serve merely to act out of spite to the admittedly-overindulgent amount of freedom that C++ gives programmers. And spite is a terrible way to drive a design process. Scala, it seems, was designed out of counter-spite: it is so different from Java, yet is based on the exact same technology stack. In my mind, Scala is the biggest "Fuck You" to Java that exists. (C# only loses out on this because it's tied so tightly to Windows that it rarely sees the light of day outside of Microsoft developer pet-projects and ASP.NET-based websites.) And when a competitor springs up just to subvert everything you've done in an ironic way? Face it, Java, you've become the new C++. And not in a good way.
So there's my Java rant. It's been a long time coming. I'll probably dissect Python at some point, so if that interests you, tune in again. 'Til then, I'll be waiting for my code to compile.