Saturday, March 26, 2011

JDK 7: The Diamond Operator

Project Coin provides numerous "small language enhancements" as a subset of the new JDK 7 features. I recently blogged on Project Coin's switching on Strings and in this post I write about the new Diamond Operator (<>).

The Diamond Operator reduces some of Java's verbosity surrounding generics by having the compiler infer parameter types for constructors of generic classes. The original proposal for adding the Diamond Operator to the Java language was made in February 2009 and includes this simple example:

For example, consider the following assignment statement:

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

This is rather lengthy, so it can be replaced with this:

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

The above example provided in Jeremy Manson's proposal (which was one of the first in response to a call for Project Coin ideas) is simple, but adequately demonstrates how the Diamond Operator is applied in JDK 7. Manson's proposal also provides significant into why this addition was desirable:
The requirement that type parameters be duplicated unnecessarily like
this encourages an unfortunate
overabundance of static factory methods, simply because type inference
works on method invocations.

In other words, the JDK 7 Project Coin addition of a Diamond Operator brings type inference to constructors that has been available with methods. With methods type inference is implicitly done when one leaves off the explicit parameter type specification. With instantiation, on the other hand, the diamond operator must be specified explicitly to "tell" the compiler to infer the type.

In his original proposal, Manson points out that syntax without a special diamond operator could not be used to implicitly infer types for instantiations because "for the purposes of backwards compatibility, new Map() indicates a raw type, and therefore cannot be used for type inference." The Type Inference page of the Generics Lesson of the Learning the Java Language trail of the Java Tutorials includes a section called "Type Inference and Instantiation of Generic Classes" that has already been updated to reflect Java SE 7. This section also describes why the special operator must be specified to explicitly inform the compiler to use type inference on instantiation:
Note that to take advantage of automatic type inference during generic class instantiation, you must specify the diamond operator. In the following example, the compiler generates an unchecked conversion warning because the HashMap() constructor refers to the HashMap raw type, not the Map<String, List<String>> type

In Item 24 ("Eliminate Unchecked Warnings") of the Second Edition of Effective Java, Josh Bloch emphasizes in bold text, "Eliminate every unchecked warning that you can." Bloch shows an example of the unchecked conversion warning that occurs when one compiles code that uses a raw type on the right side of a declaration. The next code listing shows code that will lead to this warning.

final Map<String, Set<String>> statesToCities = new HashMap();  // raw!

The next two screen snapshots show the compiler's response to the above line of code. The first image shows the message when there are no -Xlint warnings enabled and the second shows the more explicit warning that occurs when -Xlint:unchecked is provided as an argument to javac.



If Effective Java, Bloch points out that this particular unchecked warning is easy to address by explicitly providing the parameter type to the instantiation of the generic class. With JDK 7, this will be even easier! Instead of needing to add the explicit text with these type names, the types can be inferred in many cases and specification of the diamond operator tells the compiler to make this inference rather than using the raw type.

The next Java code listing provides simplistic examples of these concepts. There are methods that demonstrate instantiation of a raw Set, instantiation of a Set with explicit specification of its parameter type, and instantiation of a Set with parameter type inferred because of specification of the diamond operator (<>).

package dustin.examples;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static java.lang.System.out;

/**
 * Very simple demonstration of JDK 7's/Project Coin's "Diamond Operator."
 */
public class DiamondOperatorDemo
{
   /** Use of "raw" type. */
   private static Set<String> rawWithoutExplicitTyping()
   {
      final Set<String> names = new HashSet();
      addNames(names);
      return names;
   }

   /** Explicitly specifying generic class's instantiation parameter type. */
   private static Set<String> explicitTypingExplicitlySpecified()
   {
      final Set<String> names = new HashSet<String>();
      addNames(names);
      return names;
   }

   /** 
    * Inferring generic class's instantiation parameter type with JDK 7's
    * 'Diamond Operator.'
    */
   private static Set<String> explicitTypingInferredWithDiamond()
   {
      final Set<String> names = new HashSet<>();
      addNames(names);
      return names;
   }

   private static void addNames(final Set<String> namesToAddTo)
   {
      namesToAddTo.add("Dustin");
      namesToAddTo.add("Rett");
      namesToAddTo.add("Homer");
   }

   /**
    * Main executable function.
    */
   public static void main(final String[] arguments)
   {
      out.println(rawWithoutExplicitTyping());
      out.println(explicitTypingExplicitlySpecified());
      out.println(explicitTypingInferredWithDiamond());
   }
}

When the above code is compiled, only the "raw" case leads to a warning.


At this point, it can be insightful to look at what javap tells us about these three methods. This is done in this case with the command (-v option for verbose gives all the juicy details and -p displays these juicy details for the private methods):
javap -v -p -classpath classes dustin.examples.DiamondOperatorDemo

Because these methods were all in a single class, there is a single stream of output for the entire class. However, to make it easier to compare them, I have cut-and-paste the output into a format that aligns the javap output for each method against each other. Each column represents the javap output for one of the methods. I have changed the font color of the particular method to blue to make it stand out and label that column's output.


Other than the names of the methods themselves, there is NO difference in the javap output. This is because Java generics type erasure means the differentiation based on type is not available at runtime. The Java Tutorial on Generics includes a page called Type Erasure that explains this:
The compiler removes all information about the actual type argument at compile time.
Type erasure exists so that new code may continue to interface with legacy code. Using a raw type for any other reason is considered bad programming practice and should be avoided whenever possible.

As the quote above reminds us, erasure means that to bytecode a raw type is no different than an explicitly typed parameter type, but also encourages developers to not use raw types except for integrating with legacy code.

Conclusion

The inclusion of the diamond operator (<>) in Java SE 7 means that code that instantiates generic classes can be less verbose. Coding languages in general, and Java in particular, are moving toward ideas such as convention over configuration, configuration by exception, and inferring things as often as possible rather than requiring explicit specification. Dynamically typed languages are well known for type inference, but even statically-typed Java can do more of this than it does and the diamond operator is an example of this.

5 comments:

Martijn Verburg said...

We're also liking this new feature a good deal and suspect they might be able to push it even further in Java 8.

It was interesting when we first looked into this to see that true type inference as opposed to a simplistic 'string' copy from the LH to the RH side of the assignment as is commonly perceived.

I see you're not afraid to dive into javap :), you and Ben should definitely meet!

@DustinMarx said...

Martijn,

That'd be great if this is enhanced for Java 8! It's also nice to hear that it's true type inference. I suspected it was, but I had not confirmed that implementation detail.

I may be just a tad afraid of javap, but I'd love to become more comfortable with reading and analyzing its output.

Dustin

mcimadamore said...

Note that there are cases in which raw types and diamond lead to different bytecode:

import java.util.*;

class Foo {
Foo(X x) {}
}
class ExtFoo extends Foo {
ExtFoo(X x) { super(x); }
}

class Test {
void m(Collection l) {}
void m(List> l) {}

{ m(Arrays.asList(new ExtFoo<>(""))); //1
m(Arrays.asList(new ExtFoo(""))); //2
}
}

@DustinMarx said...

mcimadamore,

Thanks for the feedback and for providing that example.

Dustin

@DustinMarx said...

This post is referenced in the first slide in Oracle and Java 7: The Top Ten Developer Features.