10 Java Features Many Developers Haven't Heard Of

After doing code reviews recently, it dawned on me that many developers who use Java casually or as a second language aren’t familiar or intimate with some cool improvements and features that were introduced in Java 7 or earlier. In this post, I’ll list 10 features that I feel everyone programming in Java must at least know about. I’m excluding the big changes in Java 8 since everyone seem to at least know about them.

Here’s a list of features covered in this post so you could skip to the one you don’t know about or skip this post entirely if you know them all :-)

  1. The try-with-resources Statement
  2. Catch Multiple Exceptions in a Single catch Block
  3. Underscores in Numeric Literals
  4. Default Methods (in Interface)
  5. Parallel Sorting of Large Arrays
  6. Optional Return Values
  7. Strings in switch statements
  8. The Diamond Operator <>
  9. Annotations Everywhere
  10. Varargs

A word of caution: There’s no harm in doing things the old fashioned way if it is working. It is much better than rushing to use a feature that is not fully understood. But knowledge is power.

Without further ado, let’s get started.

1. The try-with-resources Statement

Prior to Java 7, working with InputStream (and other similar APIs) produced ugly looking code. Here’s an egregious example.

InputStream is = new FileInputStream("unreadme.txt");
try {
    // Read the file
} catch(IOException e) {
   // Handle if an exception occurs while reading the file
} finally {
  try {
    if(stream != null) {
      stream.close();
    }
  } catch(IOException e) {
    // Handle if an exception occurs while closing the file
  }
}

There’s a lot of noise in the code above and twin try-catch statements. It is unnecessarily verbose, even by Java standards.

The try-with-resources statement addresses this issue. You declare any object that implements java.lang.Closeable at the start of the block. When the block exits, Java automatically closes the object by calling its close() method. Let’s rewrite the above example using try-with-resources:

try(FileInputStream is = new FileInputStream("unreadme.txt")) {
  // do something with the file
} catch(IOException e) {
  //...
}

Much cleaner. You can even specify multiple Closeable objects.

2. Catch Multiple Exceptions in a Single catch Block

Since Java 7, multiple exceptions can be caught in a single catch block which makes the code less verbose and avoids duplication. Here’s an example of the multi-catch block:

try {
  // Code that throws multiple exceptions
} catch (IndexOutOfBoundsException | IOException ex) {
  logger.error("err", ex);
}

Instead of the usual:

try {
  // Code that throws multiple exceptions
} catch (IndexOutOfBoundsException oobe) {
  logger.error("trouble traversing", oobe);
} catch (IOException ioe) {
    logger.error("problem reading file", ioe);
}

Also, don’t do this:

try {
  // Code that throws multiple exceptions
} catch (Exception e) {
  logger.error("an error occurred", e);
}

Catching Exception is generally a bad idea. The catch block in the example above will catch all exceptions that are thrown in the try body including the ones that it cannot handle. This prevents upper methods in the stack from handling the exception properly.

The only catch is that the exceptions in multi-catch must be disjoint. See this Stack Overflow answer for more details.

3. Underscores in Numeric Literals

Starting from Java 7, you can use underscores in numeric literals to make your code more readable. The underscores can appear anywhere between the digits, except at the start or at the end of literal. Here are some examples taken from Oracle’s tutorial:

long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
long phoneNumber = 123_456_7890L;
long data = 0b11010010_01101001_10010100_10010010;
byte nybbles = 0b0010_0101;
long maxLong = 0x7fff_ffff_ffff_ffffL;
int x6 = 0x5_2;

Although, I’m not sure if it would ever make sense to store credit cards or social security numbers in your code, using underscores certainly make reading hex and binary variables a lot more convenient.

4. Default Methods (in Interface)

Since Java 8, you can include method bodies to interfaces which wasn’t allowed in the previous versions of Java. These methods are known as the default methods because they are automatically included in classes that implement the interface. Default methods were added primarily for backwards compatibility reasons and you should use them judiciously.

This blog has a good overview of the subject.

5. Parallel Sorting of Large Arrays

This one’s my favorite. It allows larger arrays to be sorted faster. It works by dividing a large array into several smaller subarrays and then sorting each subarray concurrently or in parallel. The results are merged back together to form the answer. So instead of,

Array.sort(someArray); // Sorts arrays sequentially

Starting with Java 8, you could use:

Arrays.parallelSort(someArray); // Parallel Sorting

The analysis done for this article of Dr. Dobb’s got 4x better performance:

I loaded the raw integer data from an image into an array, which ended up at 46,083,360 bytes in size (this will vary depending on the image you use). The serial sort method took almost 3,000 milliseconds to sort the array on my quad-core laptop, while the parallel sort methods took a maximum of about 700 milliseconds. It’s not often that a new language release updates the performance of a class by a factor of 4x.

4x!? I’d be very pleased with anything over 2x.

6. Optional Return Values

Java 8 has introduced a container object called Optional for wrapping objects that may not be present or null. A method can wrap its return type in Optional. Let’s look at an example:

public static Optional<User> getUser(String id) {
  // If the user id is NOT FOUND, return null
  return null;
}

And here’s how to call the method:

Optional<User> optional = getUser("jhal1");
if (optional.isPresent()) {
  // User found. Get the value
  User user = optional.get();
} else {
  // No user found
}

In case you are wondering, what’s wrong with just returning the null value? Callers aren’t always aware that method may return null and do not always check for it. This happens frequently and is one of the reasons why the NullPointerExceptions are so plentiful. So, if you are tired of null pointer exceptions, use Optional.

7. Strings in switch statements

I almost forgot that Java has a switch-case statement. Prior to Java 7, switch-case statement only worked with integer types (except long) and enums. Java 7 introduced the ability to use a String object as the expression. Here’s how it looks:

String car = getCar();

switch(car) {
  case "Corvette":
  //Handle Corvette
  break;
  case "AC Cobra":
  //Handle AC Cobra
  break;
  case "McLaren F1":
  //Handle McLaren F1
  break;
  default:
  //Handle "Car not Found" error
  break;
}

Which is equivalent to the more cluttered:

if (car.equals("Corvette")) {
  //Handle Corvette
} else if (car.equals("AC Cobra")) {
  //Handle AC Cobra
} else if (car.equals("McLaren F1")) {
  //Handle McLaren F1
} else {
  //Handle "Car not Found" error
}

8. The Diamond Operator <>

Diamond operators were introduced to make the use of generics a little less verbose. Take a look at this example:

Map<String, List<String>> aMap = new HashMap<String, List<String>>();

The parameter types are duplicated on the left and right sides of the expression. Since Java 7, you can omit the type definitions on the right side of assignment expressions with a diamond operator, <>. The above statement could be rewritten as:

Map<String, List<String>> aMap = new HashMap<>();

When the compiler encounters the diamond operator (<>), it infers the generic type arguments from the context.

9. Annotations Everywhere

Thanks to Java 8, annotations can be retrofitted almost anywhere in your code. Great, because that’s just what we needed. I’m fine with annotations, but I’ve seen some developers going overboard, trying to do too much magic with annotations. Too much of annotations, just like too much of anything, are bad. You’re probably better off not knowing that this feature even exists. End of my rant.

10. Varargs

Varargs are useful for passing an arbitrary number of parameters to a method. Such as String.format(String format, Object...args). Joshua Bloch in Effective Java recommends using varargs judiciously:

varargs are effective in circumstances where you really do want a method with a variable number of arguments. Varargs were designed for printf, which was added to the platform in release 1.5, and for the core reflection facility (Item 53), which was retrofitted to take advantage of varargs in that release. Both printf and reflection benefit enormously from varargs. You can retrofit an existing method that takes an array as its final parameter to take a varargs parameter instead with no effect on existing clients. But just because you can doesn’t mean that you should!

That’s sound advice. Just because there’s a feature available, doesn’t mean you have to use it.

This article was written by Umer Mansoor. Please leave your comments below and like on Facebook or follow on Twitter to stay up-to-date.

Subscribe to CodeAhoy

Join today and be the first to get notified on new updates.

comments powered by Disqus