Java Pain Points
Java has been declared either dead or dying multiple times thoughout it’s life. For Java programmers unaware of the debate, this can be very disconcerting. I have used Java for the majority of my programming life and from my observations Java programmers are ignorant of other languages. However, great Java programmers are the languages loudest critics, but what are the harshest Java pain points?
-
Explicit typing
-
Generic type erasure
-
Lambdas
-
Tail recursion
-
Contracts
-
Continuations
-
Meta-programming
-
Limited obvious types
-
Lack of unified type system
-
Immutability
-
Laziness
-
Pattern matching
-
Boilerplate
-
Concurrency
-
Multiple inheritance
Explicit Typing
Dynamic/static typing and type inference are often confused. Statically typed languages can have type inference (e.g. Scala, Haskell and to a limted extent C#). We have known about the Hindler-Milner type inference algorithm since 1969 (43 years) yet very few statically typed languages have type inference. I predict this will gain more attention in the coming years.
Generic Type Erasure (Reification)
The problem with generic type erasure is shown nicely in Josh Bloch’s book Effective Java (Chapter 5 - Generics). Since arrays are erroneously covariant instead of invariant, generics should be used to improve your code for (weak) type safety. Some problems with generics: * Calling a method with a generic argument defaults the type of the argument to Object at runtime, calling a method that the programmer did not expect * Cannot create objects of the generic type * Others
Lambdas
Lambdas are coming to Java in version 8 (as of today, due mid 2013). This will help ease generic programming in Java and the verbosity when using anonymous inner classes for simple function callbacks. This is explained reasonably in this InfoQ article Lambas in Java: An In-Depth Analysis.
Tail Recursion
Tail recursion and tail call optimisation (TCO) are critical for functional programming and eliminating the repetition involved in loops (replace with maps and folds). The Scala compiler converts tail calls to loops using TCO. I have yet to see plans to include tail recursion in the JVM, but there seems to be some active research into how this can be done.
Contracts
Java has just the simple assert mechanism. Support for Design by Contract used to be in the top 10 (actually second) most requested features, however the request for enhancement 4449383 seems to have been closed. Thinking broadly; proofs, types, contracts and unit tests have the following relationship to a program:
-
Proofs ⇒ General and strong theorems produced statically (but hard to prove and expensive to do)
-
Types ⇒ General but weak theorems (usually checked statically)
-
Contracts ⇒ General and strong theorems, checked at run time for particular instances (can be turned on or off)
-
Unit testing ⇒ Specific and strong theorems, checked quasi-statically on interesting cases
These theorems are not well integrated in programming languages (presenting research opportunities).
Continuations
Continuations would make web programming dead simple. Just restore the state and continue the computation.
Meta-programming
Poorly supported in Java, particularly compared to the dynamically typed languages Lisp, Erlang, Ruby, Groovy, etc.
Limited obvious types
The Java Date and Time API is a mess. Sure we can use Joda-Time until JSR 310 gets released (perhaps in Java 8), but this is a fundamental language type and it should be in the core library.
The double type is a poor implmentation of the decimal type IEEE 754. Consider the Java program below which shows mutliples of 0.1.
package mperry;
public class DoublePrecision {
public static void main(String[] args) {
final int max = 20;
final double multiplier = 0.1;
final int wrap = 10;
for (int i = 0; i < max; i++) {
double d = i * multiplier;
if (i % wrap == 0) {
System.out.println();
}
System.out.print(i + ": " + d + " ");
}
System.out.println();
}
}
Which produces the following output:
0: 0.0 1: 0.1 2: 0.2 3: 0.30000000000000004 4: 0.4 5: 0.5 6: 0.6000000000000001 7: 0.7000000000000001 8: 0.8 9: 0.9 10: 1.0 11: 1.1 12: 1.2000000000000002 13: 1.3 14: 1.4000000000000001 15: 1.5 16: 1.6 17: 1.7000000000000002 18: 1.8 19: 1.9000000000000001
Also missing is simple tuples (see Python) and the Option type (for type safe handling of nulls - no more NullPointerException - EVER).
Unified Type System
The Java type system is a hodge-podge of different abstractions that lacks an elegant, unified model. You need to work with arrays, classes and primitives. Compare this to Scala’s type system.
Java arrays are a pain to work with and are unfortunately covariant. Primitves have to be boxed and unboxed and interact strangely with var args and generics. Ugly!
Immutability
Java uses the final keyword to indicate immutability. There are a few problems with this.
-
Immutability should be the default.
-
For objects, the immutability only applies to the reference, not the instance variables it contains.
Thus there is no way to indicate this a method is pure (is referentially transparent). Even C++ could indicate this with it’s const keyword.
Laziness
Laziness in programming languages is really useful. From wikipedia on lazy evaluation:
Performance increases due to avoiding unnecessary calculations and avoiding error conditions in the evaluation of compound expressions.
The capability of constructing potentially infinite data structures
The capability of defining control structures as abstractions instead of as primitives.
Without referential transparency, I don’t see how it can ever include lazy evaluation without building this into the language primitives, ala short circuit evaluation.
Pattern matching
I don’t mean regular expression pattern matching, but structural pattern matching using algebraic data types. Commonly the Interpreter and Visitor pattern is used to simulate pattern matching, but this gets ugly - fast!
Boilerplate
This can involve: * automatic resource management (see Better Resource Management with Java SE 7: Beyond Syntactic Sugar) * properties (see Project Lombok) * missing list and map literals (note: this is now available in Java 7) * poor data representation that results in reams of XML configuration * others?
Concurrency
The fork/join framework is a step in the right direction, but still dominant is the lock/semaphore/monitor paradigm created in the 60s over 40 years ago. How many people are sure their concurrent code is correct in all situations? Software transactional memory implementations look like they exist in Java, but I have not tried any myself.
Multiple inheritance
Eiffel did this right, but C++ created nightmares with diamond inheritance which tainted the entire concept. Scala seems to be bringing this back in a limited way with traits.
…and I haven’t even mentioned the modularity coming in Java 8.