Saturday, July 30, 2011

Java 7: Decoding the new Diamond operator with JDT

Java 7 GA is officially out. Many people have already been trying their hands on the new features, and blogging/commenting about them at various forums. However, I still believe that people do not understand the real meaning of the diamond operator, which brings in "improved type inference for generic instance creation". Being the guy responsible for implementing this feature in Eclipse JDT's BETA java 7 support, I had a tough time understanding the specs at first. But now I have greater clarity. I guess everybody knows by now that diamond operator <> can be used in place of re-specifying type arguments in a class instantiation expression / constructor invocation. This makes the following case quite clear:

Instead of writing the following in java 1.6 and below
List <String> list = new ArrayList<String>();

one can now simply write
List<String> list = new ArrayList<>();

and the compiler will automatically infer that you meant to write ArrayList<String>();

We also know by now, that this is different from
List <String> list = new ArrayList();

since here ArrayList() instantiates a raw type, and the compiler, with -Xlint:unchecked will warn for an unchecked conversion in the above statement.

So far, so good. However, it is worth noting that there's more to it than meets the eye, and people often take a simplistic view of the diamond usage. It is worth remembering that the diamond inference works just like type inference for generic methods, where instead of using <>, it is enough to just forget about specifying anything at all, and leave the inference to the compiler. This is unfortunately or fortunately (depending on whether you prefer to lose your hair or not) a consequence of allowing the diamond operator in ANY class instance creation expression.

Let's take a closer look, and try to understand the various scenarios of diamond usage with the help of Eclipse snippets. The easier part first - let us investigate a couple of cases(i.e. instantiation expressions) where the diamond operator CANNOT be used.
  1. Anonymous class declarations                                        
  2. Explicit type arguments for the constructor: Lets try to understand this further. A constructor can have two sets of type arguments. A constructor in a parameterized type can have type arguments of the class as its type arguments + it may declare its own. In the example shown below, constructor of Diamond uses T, which is the class type argument and also declares another type argument U. In case the constructor also declares its own type arguments, type parameters will be explicitly specified at the constructor invocation. If this happens, and yet the constructor invocation uses the diamond operator to let the compiler infer the first set of types args (i.e. those coming from the class), then it is a compile time error.                                                                             
Now comes the trickier part. Lets see the valid use cases of the diamond operator. Consider the following (simplest) case and let us see how the inference of type arguments work.

This case is straightforward. The constructors are pretty much chosen by the standard overloading mechanism.  However, this might create the simplistic view that the compiler simply substitutes the type from the RHS in place of the <> operator i.e. in the above case, <> is substituted as String and hence T = String. So all constructors are valid. Let us try understand how this works with a tweaked example:

Oops! This gives a compile error, even though going by the above simplistic view, at line 15, <> should be inferred as Number and hence, T= Number, which means this should be valid. However, even though simple, this is not the way the compiler infers type for the diamond. On the contrary, it tries to :
  1. find the matching constructor call with 1 argument, which in the above case, means DiamondTest(T t)
  2. It then substitutes the formal parameters of this constructor with the parameters that have been passed at the call site. So, T becomes Integer. (In case of default constructor, or where no type argument is substituted in the matching constructor, by default T gets inferred as Object).
  3. Using the inferred type arguments from 1 and 2, the compiler determines the type of the instantiation expression and the exceptions thrown. Hence in this case, new DiamondTest<>(1) gets translated as new DiamondTest<Integer>(1).
Hence, the compile error.  Now let us see some more use cases of the diamond operator:
  • Inner classes

Note how the statement at line 21 is not allowed since the new DiamondTest<>() is inferred as new DiamondTest<Object>(). Also, note that as of today, even statement 20 does not work with Oracle Javac because of a bug in their compiler. However, Eclipse JDT compiler correctly compiles it! :)

  • Non-variable declaration statements

  • Return statements
  • Conditional operators

However, note how the following results in an error

We can construct many more scenarios, but in the interest of time (yours and mine ;) ), I'll  leave it here. Go ahead, experiment with the diamond with Eclipse. The Eclipse work for JAVA 7 that was being done in the BETA_JAVA 7 branch has now been merged into the HEAD and R_3_7_maintenance streams and so, you can try next week's builds in either 3.8, 3.7.x or 4.2 streams to get your hands dirty on Java7. Some new JDT features with respect to the diamond operator are:
  • Content assist is diamond aware. Wherever legal and non-ambiguous, content assist will insert <> and not the type arguments. Ex: List<String> l = new Arra|  is completed to List<String> l = new ArrayList<>();
  • Configurable compiler warning on redundantly specified type arguments.
  • Quick assist (CTRL+1) to insert type arguments 
For a preview of more new Java 7 features in Eclipse JDT, check out Deepak's blog. If you find any bugs, please open a bug with [1.7] in the summary. Most of the Eclipse JDT team will be at IBM Rational Innovate India 2011, Bangalore on August 10 and 11, 2011. Catch us there!