Navigation

Java SE 5 is the most significant release

If I were to pick one version of Java whose features I could not live without, it would easily be version 5 (released Sep 2004, officially known as J2SE 5.0). I started learning real programming and the Java programming language around 2002, using version 1.4 for those first few years of software development. Back in those days, I didn’t learn to use the standard data structure classes in the collections framework. Instead, I manually wrote dynamically sized arrays for specific types, which was quite painful (but sort of type-safe, and avoids iterators and casting).

I began adopting version 5 around 2004, but it took me the next few years to fully embrace its new features like generics, for-each loop, auto-boxing, varargs, printf(), etc. Later versions of Java (particularly 7 and 8) add a number of features that I enjoy, but I consider them to be nice-to-haves, rather than core features that make the Java programming language bearable to use.

Java SE 5 features that I use

The following features are listed in roughly descending order of personal importance, and explained with a usage example from my experience. This is not meant to be an exhaustive list of all features added in version 5; there are links to publicly available documentation for that information.

Generics make the element types visible to the programmer, and are checked by the compiler. After compilation though, generics are translated into raw types and casting, which means that the two pieces of example code behave identically from the JVM’s point of view.

For-each loop

The old way of iterating forward through language-level arrays and standard library containers:

The for-each loop drastically cuts down on the amount of verbose syntax in both cases. For the array case, some small ad hoc arrays are meant to be traversed from front to back – but many arrays need advanced control over the indexing (e.g. offset, length, stride, etc.), so using explicit loop variables is not considered unusual. For container classes that implement Iterable<E> (such as List<E> and Set<E>), it is very common to use the for-each syntax, and almost the only reason to use an explicit iterator is to be able to call remove() on elements.

The traditional for-loop, which comes from the C programming language, is rather low-level with its three sections and explicit mechanisms. By contrast, the for-each loop expresses the programmer’s high-level intent more succinctly, and is the type of for-loop found in Python and shell scripting languages.

Auto-boxing/unboxing

Java has an uncomfortable divide between primitive numeric types and object-boxed versions of these types (e.g. float vs. java.lang.Float). I personally don’t know if this language feature can be designed better (see also: C/C++ values vs. pointers, C# generics, and Java’s own Project Valhalla), but auto-boxing certainly reduces the syntactical pain in a low-cost, low-risk way. Auto-boxing plays well with generics and the for-each loop (both introduced in Java 5), making it far less painful to put int values into a List<Integer> for example. However, auto-boxing has some pitfalls when using the equality operator (==) and dealing with null values.

Variadic methods

Old example without the feature (a.k.a. varargs):

int sum(int[] arr) {
(...)
}
print(sum(new int[]{1, 2, 3}));

With varargs, the method declaration is changed slightly and the caller’s code is simplified (but in the JVM it behaves exactly like the old example):

int sum(int... arr) {
(...)
}
print(sum(1, 2, 3));

The C function printf() is a flexible, powerful, and concise way to express string formatting logic. Varargs enable this function to be included in Java with a sane syntax, made available in PrintWriter/PrintStream.printf() and String.format(). I do make use of these methods infrequently, and they are more desirable than doing things in a way that avoids varargs.

Return type covariance

Previously, it was impossible for a subclass to override a method to have a more specific return type:

Note that when implementing clone(), return type covariance will help tremendously when you clone sub-objects and arrays that your class contains.

Annotations

Annotations mark classes, methods, fields, local variables, etc. with additional information that can be used at compile time and/or run time. Although the annotation mechanism required a change in the Java programming language, each annotation definition does not – hence annotations are more lightweight than language features.

I also use SuppressWarnings for various compile-time messages like raw types, unsafe generics, serialVersionUID, unused variables, etc. I am still undecided about whether using the Override annotation is a good or bad thing.

Enumerations

Enums are a great type-safe alternative to sequential constants (e.g. NORTH=0, EAST=1, SOUTH=2, WEST=3) and bit flags (e.g. BOLD=1, ITALIC=2, UNDERLINE=4). A basic, no-frills enum is very easy to declare:

You can use these enum values in equality comparisons (e.g. if (dir == EAST)) and in switch statements. Moreover, each enum value is assigned an integer value starting from 0, which can be obtained by calling the ordinal() method (e.g. WEST.ordinal() == 3). This allows you to manually convert a strongly typed enum value into a plain integer. Conversely, you can call your enum type’s static values() method to get an array of all enum values in the declared order.

As an alternative to carefully defining bit flag constants and doing arithmetic on them (which seems to be a pastime of C/C++ programmers), you should definitely declare an enum type and put values into an EnumSet (which is even more convenient than BitSet).

A Java enum is a full-fledged class, and can have fields and methods (in addition to the implicit ones already mentioned). Even before Java 5, this view of enums as classes was already advocated by Joshua Bloch’s book Effective Java (1st edition), item 21 “Replace enum constructs with classes”. Enums are a concise way to implement the multiton pattern. (Whereas in C/C++, an enum is not much more than a way to define integer constants with auto-incrementing values. At least enums are type-safe in C++ and won’t implicitly cast to int.)

Standard library

Integer adds functions such as bitCount(), rotateLeft(), numberOfLeadingZeros(), which are useful in occasional low-level arithmetic (e.g. cryptography) and bitwise manipulation (e.g. packing data fields into bits). As a bonus, almost every function is intrinsified into a single machine instruction (at least on x86/x86-64).

The new java.util.concurrent package and subpackages add tons of classes for building multithreaded/concurrent/parallel applications. I have only used a few of them personally (like ReentrantLock, Condition, AtomicInteger, AtomicLongArray, BlockingQueue, CountDownLatch, ExecutorService), but they are tremendously helpful. This is because without these classes, the alternative is either to implement concurrent data structures and algorithms (e.g. BlockingQueue) based on Java’s primitive monitor, or (even worse) use Unsafe or even JNI to implement some of these features at all (e.g. a Lock with multiple Condition variables).

Other Java 5 things I like from the standard library are already covered in the section on varargs (such as printf()).

Static imports

Before, if you wanted to call a static method of another class without prefixing the class’s name, you either needed to subclass that class (impure) or write your own proxy method (verbose):

Now, it is a simple matter of adding import static Factorial.gamma; or import static Factorial.*; to the top of your source file. This helps with frequently used functions or constants like Math.sin() or Math.PI. In the case of the old JUnit 3, your test suite extends TestCase which contains static methods such as assertEquals(). But in JUnit 4, your test suite doesn’t need to extend anything, and you can statically import org.junit.Assert.assertEquals() to achieve the same convenient effect.

Java SE 6 features that I use

Java 6 was released only a year after Java 5, whereas versions 5, 7, and 8 took three or more years of development. This version had improvements behind the scenes, but not many affected me personally in practice.

Standard library

The new ArrayDeque<E> class is what I reflexively grab when I need a plain Queue<E> (not to be confused with a PriorityQueue<E> or heap). Previously in Java 5, the only real choice for a queue was LinkedList<E>, ignoring all the concurrent and blocking implementations.

The Arrays utility class adds the family of copyOf(), copyOfRange(), and binarySearch() functions, which are quite useful for manipulating low-level array buffers.

Java SE 7 features that I use

Java 7 introduces a handful of syntactic and library conveniences, which I appreciate a decent amount. These features are definitely more significant than Java 6, and for now they play a much bigger role in my work than Java 8 features.

Generic diamond operator

Although Java 5’s generics improve safety and documentation, the redundant verboseness has always been a sore point that many programmers complain about.

Java 5 already has type inference when calling methods that have type parameters (e.g. Collections.max()), but does not apply to constructors. This means we could actually have even less syntax at the call site by implementing a helper function:

The diamond operator is not the same as omitting it, i.e. not the same as raw types. Raw types are unsafe and generate lots of compile-time warnings. The diamond operator infers the generic type only when it is safe and unambiguous to do so.

This sort of type inference in Java applies to the value being constructed, not to the variable being assigned. It is the opposite behavior of type inference like var x = new List<int>(); (C# 3.0 and above) or auto it = myvec.cbegin(); (C++11 and above).

Try-with-resources statement

Also known as automatic resource management (ARM), this reduces the syntactic burden of writing a finally block and a call to close(). For example:

I write explicit null checks at the beginning of many methods as a defensive programming practice. Now instead of writing if (obj == null) throw new NullPointerException();, I simply write Objects.requireNonNull(obj);.

Objects.hash() makes it quick and easy to implement a good-enough hashCode() method in your custom class. Of course if hash speed or uniformity were important, you would still write custom hashing logic.

Integer.compare(), Short.compare(), etc. make it easier to write your own compareTo() methods or Comparators that examine primitive integer values. Previously, you either had to subtract numbers (could overflow), or test the three cases <, ==, > manually, or box the values into objects to call compareTo(). Curiously though, Float.compare() and Double.compare() have existed since Java 1.4.

The new Path objects (which are like File objects) can be used in the new Files utility class. Files provides powerful functions like copy(), readAttributes(), readAllBytes(), readAllLines(), write(), and much more.

Java SE 8 features that I use

As of writing (Jul 2017), I have been aware of Java 8 features for only about a year (despite the technology being released in Mar 2014). Java 8 is by far the most significant release since SE 5, and might have more raw features and changes than version 5. However, the number of Java 8 features I have adopted and chosen to rely on is much fewer than Java 5 features.

There is still much Java 8 functionality for me to discover, especially in lambdas and streams. Also, some computer systems (such as certain Linux distributions) only provide up to JDK 7, so when I publish code for a wide public audience, I generally stay away from Java 8 features because they might hinder acceptance of my programs.

Better generic type inference

Java 7’s diamond operator is a leap forward in convenience and conciseness, but it doesn’t cover all the cases. For example, the Java 7 compiler makes this incorrect inference:

When creating an anonymous inner class within a method, the rule is that it can only capture local variables (and parameters) that are final. In Java 7 and earlier, the captured variables/parameters must be literally declared as final, but Java 8 relaxes this restriction so that effectively final variables are allowed too. Thus, we can reduce syntactical noise when writing anonymous classes and (Java 8 new) lambda functions.

Standard library

Base64’s encoder and decoder are really helpful in those few times that Base64-encoded text comes up in web/HTTP/email programming. It’s not hard to write your own encoder or decoder from scratch (around 20 lines per direction), but why go through the pain and risk having bugs?

Integer adds functions to treat the 32-bit int type as an unsigned value, and likewise for Long. This comes up rarely (like in making your own bigint implementation), but it’s still good to see official library support for these features. The functions can be emulated with a modest amount of knowledge and effort, and these ideas are discussed on my page Unsigned int considered harmful for Java. (I argue that adding new primitive unsigned types like uint is bad, but adding functions to treat the existing int as unsigned is just fine.)