Java 7

Java 7 has some performance benefits and new features that many programmers have been expecting for years.

Language Updates

The following features have been added to Java the language:

  • Diamond Operator
  • Strings in switch
  • Automatic resource management
  • Improved Exception handling
  • Numbers with underscores

Diamond Operator

The Diamond Operator simplifies the declaration of generic classes. The generic types are inferred from the definition of the field or variable. For example, in the following code, the second line is now equivalent to the first in Java 7:

1 Map<String, List<Double>> nums = new HashMap<String, List<Double>> ();
2 Map<String, List<Double>> nums = new HashMap <> ();

Strings in Switch

You can now use Strings in switch statements. For example, the following code would compile and work in Java 7:

 1 public static <T> Collection<T> makeNew(String type, Class<T> tClass) {
 2 	switch (type) {
 3 	case "set":
 4 		return new HashSet<>();
 5 	case "lset":
 6 		return new LinkedHashSet<>();
 7 	case "treeset":
 8 		return new TreeSet<>();
 9 	case "vector":
10 		return new Vector<>();
11 	case "array":
12 		return new ArrayList<>();
13 	case "deque":
14 	case "queue":
15 	case "list":
16 	default:
17 		return new LinkedList<>();
18 	}
19 }

As seen above, Strings can now be used just like any primitive would in a switch statement.

Automatic resource management

The new Automatic resource management feature makes dealing with resources, such as files, much easier. Before Java 7 you needed to explicitly close all open streams, causing some very verbose code. Now you can just do the following:

1 public void writeWithTry() {
2 	try (FileOutputStream fos = new FileOutputStream("books.txt");
3 			DataOutputStream dos = new DataOutputStream(fos)) {
4 		dos.writeUTF("Modern Java");
5 	} catch (IOException e) {
6 		// log the exception
7 	}
8 }

Improved Exception handling

Improved Exception handling in Java 7 means that you can catch more than one exception in one catch statement. Previously, you had to write a different catch block for each exception. This may seem trivial, but will make Java development somewhat easier. Here’s an example of the new style:

 1 public static Integer fetchURLAsInteger(String urlString) {
 2 	try {
 3 
 4 		URL url = new URL(urlString);
 5 		String str = url.openConnection().getContent().toString();
 6 		return Integer.parseInt(str);
 7 
 8 	} catch (NullPointerException | NumberFormatException | IOException e) {
 9 		return null;
10 	}
11 }

The above code would fetch content from the given url and attempt to convert it to an Integer. If anything goes wrong it returns null. Although this is a contrived example, similar situations do occur in real code.

Numbers with underscores

Numbers with underscores is exactly what you think. Humans have a hard time reading long streams of numbers, so Java 7 allows you to put underscores in numeric literals to make them easier to understand. For example, three million would be written as follows:

1 int lemmings = 3_000_000;

Fork/Join

There are new Java concurrency APIs (JSR 166y) referred to as the Fork-join framework. It is designed for tasks that can be broken down and takes advantage of multiple processors. The core classes are the following (all located in java.util.concurrent):

  • ForkJoinPool: An ExecutorService for running ForkJoinTasks and managing and monitoring the tasks.
  • ForkJoinTask: This represents the abstract task that runs within the ForkJoinPool.
  • RecursiveTask: This is a subclass of ForkJoinTask whose compute method returns some value.
  • RecursiveAction: This is a subclass of ForkJoinTask whose compute method does not return any value.

As an example of using this framework, let’s find the sum of 2000 integers. This is a trivial example but will hopefully demonstrate proper use of the ForkJoin framework.

In this example we will divide the array of integers in half and assign each half to a RecursiveTask. If the array size is less than 20 elements then we assign it to another RecursiveTask that computes the sum of the array.

Here is the RecursiveTask for computing the sum:

 1 class SumCalculatorTask extends RecursiveTask<Integer>{
 2 	int [] numbers;
 3 	SumCalculatorTask(int[] numbers){
 4 		this.numbers = numbers;
 5 	}
 6 	
 7 	@Override
 8 	protected Integer compute() {
 9 		int sum = 0;
10 		for (int i : numbers){
11 			sum += i;
12 		}
13 		return sum;
14 	}
15 }

The compute method has to be overridden with the actual task to be performed. In the above case its iterate through the elements of the array and return the computed sum.

We create a RecursiveTask for dividing the array into two parts and assign each part to another RecursiveTask for further dividing. We continue dividing the array and stop dividing when the array has less than 20 elements.

 1 class NumberDividerTask extends RecursiveTask<Integer>{
 2 	int [] numbers;
 3 	NumberDividerTask(int [] numbers){
 4 		this.numbers = numbers;
 5 	}
 6 	
 7 	@Override
 8 	protected Integer compute() {
 9 		int sum = 0;
10 		List<RecursiveTask<Integer>> forks = new ArrayList<>();
11 		if (numbers.length > 20){
12 			NumberDividerTask task1 =
13 				new NumberDividerTask(Arrays
14 				  .copyOfRange(numbers, 0, numbers.length/2));
15 			NumberDividerTask task2 = 
16 				new NumberDividerTask(Arrays
17 				  .copyOfRange(numbers, numbers.length/2, numbers.length));
18 			forks.add(task1);
19 			forks.add(task2);
20 			task1.fork();
21 			task2.fork();
22 		} else {
23 			SumCalculatorTask sumCalcTask = new SumCalculatorTask(numbers);
24 			forks.add(sumCalcTask);
25 			sumCalcTask.fork();
26 		}
27 		//Combine the result from all the tasks
28 		for (RecursiveTask<Integer> task : forks) {
29 			sum += task.join();
30 		}
31 		return sum;
32 	}
33 }

The above NumberDividerTask spawns either two other NumberDividerTask’s or a SumCalculatorTask. Each task keeps a track of the sub-tasks it has created. At the end of the task we wait for all the tasks in the forks list to finish by invoking the join() method and compute the sum of those values returned from the sub-tasks.

To invoke the above defined tasks we make use of ForkJoinPool and create a NumberDividerTask task by giving it the array whose sum we wish to compute.

 1 public class ForkJoinTest {
 2 	static ForkJoinPool forkJoinPool = new ForkJoinPool();
 3 	public static final int LENGTH = 2000;
 4 	
 5 	public static void main(String[] args) {
 6 		int [] numbers = new int[LENGTH];
 7 		// Create  an array with some values. 
 8 		for(int i=0; i<LENGTH; i++){
 9 			numbers[i] = i * 2;
10 		}
11 		int sum = forkJoinPool.invoke(new NumberDividerTask(numbers));
12 	  
13 		System.out.println("Sum: "+sum);
14 	}
15 }

After running the above code the output should be: Sum: 3998000.

Although this is a simple example, the same concept could be applied to any “divide and conquer” algorithm.

New IO (nio)

Java 7 adds several new classes and interfaces for manipulating files and file-systems. This new API allows developers to access many low-level OS operations that were not available from the Java API before, such as the WatchService and the ability to create links (in *nix operating systems).

The following list defines some of the most important classes and interfaces of the NIO API:

Files
This class consists exclusively of static methods that operate on files, directories, or other types of files.
FileStore
Storage for files.
FileSystem
Provides an interface to a file system and is the factory for objects to access files and other objects in the file system.
FileSystems
Factory methods for file systems.
LinkPermission
The Permission class for link creation operations.
Paths
This class consists exclusively of static methods that return a Path by converting a path string or URI.
FileVisitor<T>
An interface for visiting files.
WatchService
An interface for watching varies file-system events such as create, delete, modify.

Using a WatchService

To watch a directory you would register a Path object with the WatchService, as follows:

1 // import the standard events: ENTRY_MODIFY, ENTRY_DELETE, etc.
2 import static java.nio.file.StandardWatchEventKinds.*;
3 import java.nio.file.*;
4 // later on in some method...
5 Path path = Paths.get("/usr/local");
6 WatchService watchService = FileSystems.getDefault().newWatchService();
7 WatchKey watchKey = path.register(watchService, ENTRY_CREATE);

This registers the WatchService to watch the given path. For example, if you register the directory “/usr/local” as above, the WatchService will be notified whenever a file is created in that directory.

To monitor events you can use either the take or poll method of the WatchService. The first is a blocking call, and second, poll, is non-blocking and returns null if no events are avaiable. Keep in mind that take will block the Thread until something happens. Both methods return a WatchKey instance that needs to be reset by calling reset before continuing.

For example, the following code loops forever calling take and doing something with each WatchEvent that is returned:

 1 while (true) {
 2 	WatchKey key = watchService.take();
 3 	List<WatchEvent<?>> pollEvents = key.pollEvents();
 4 	try {
 5 		for (WatchEvent<?> event : pollEvents) {
 6 			Path p = (Path) event.context();
 7 			// do something with p
 8 		}
 9 	} catch (Exception e) { e.printStackTrace();
10 	} finally { key.reset(); }
11 }

JVM Benefits

Java 7 adds some new features to the JVM, the language, and the runtime libraries.

The JVM has the following new features:

  • Serviceability features (JRockit/hotspot convergence)
    • Java Mission Control (monitor, manage, profile)
    • Java Flight Recorder (profiling, problem analysis, debugging) (in progress)
  • jdk introspection
    • jcmd - list running java processes
    • jcmd <pid> GC.class_histogram - size of classes
  • Better garbage collection.

Performance Benefits

There are also Performance Benefits in the JVM and runtime libraries:

  • Runtime compiler improvements.
  • Sockets Direct Protocol (SDP)
  • Java Class Libraries
    • Avoid contention in Date: changed from HashTable to ConcurrentHashMap
    • BigDecimal improvements (CR 7013110)
  • Crypto config. files updates, CR 7036252
    • User land crypto for SPARC T4
    • Adler32 & CRC32 on T-series
  • String(byte[], string) and String.getBytes(String) 2-3x performance.
  • HotSpot JVM
    • updated native compilers -XX:+UseNUMA on Java 7 (Linux kernel 2.6.19 or later; glibc 2.6.1).
    • Partial PermGen removal (full removal in JDK 8) -interned String moved to Java heap.
    • Default Hashtable table size is 1009 increase size if needed -XX:StringTableSize=n
    • Distinct class names: XX:+UnlockExperimentalVMOptions -XX:PredictedClassLoadCount=#
  • Client library updates (Nimbus Look&Feel; JLayer; translucent windows, Optimized 2d rendering)
  • JDBC 4.1 updates (allow Connection, ResultSet, and Statement objects be used in try-with-resources statement)
  • JAXP 1.4.5 (Parsing) (bug fixes, conformance, security, performance)
  • JAXB 2.2.3 (Binding)
  • Asynchronous I/O in java.io for both sockes and files (uses native platform when available)
  • x86 (intel) improved 14x over 5 processor releases (jdk5 jdk 6)
  • JDK 7u4 faster than Java6 and JRockit.

Backwards Compatibility

There are some issues to watch out for when upgrading to Java 7 on a large project:

  • More stringent bytecode verifier for Java 7 (only issue when doing bytecode modification; work-around -XX:-UseSplitVerifier)
  • Order of methods return from getMethods() has changed (not guaranteed to be in declaration order)