Tuesday, January 31, 2012

Java 7 - Project Coin Decompiled Part II

Hello everybody, this is the second part of Java 7 - Project Coin Decompiled, as you remember, we have already seen some of the small changes introduced by Project Coin in Java 7. In this post, we are going to decompile those examples from the last post in order to see what's the compiler doing for us.


What you need
NetBeans 7+ or any other IDE that supports Java 7
Diamond Operator
This is the example of diamond operator we just saw in the last post:

//suppose the classes Region, Country, City and Customer exist
import java.util.*;
...
Map<Region,Map<Country,Map<City,List<Customer>>>> map = new HashMap<>();
...

Now, let's see what's the generated code by the compiler looks like:

import java.util.*;
...
java.util.Map map = new HashMap();
...

Just an old school map definition and instantiation... Why? Because that's how generics works:

When you take an element out of a Collection, you must cast it to the type of element that is stored in the collection. Besides being inconvenient, this is unsafe. The compiler does not check that your cast is the same as the collection's type, so the cast can fail at run time.

Generics provides a way for you to communicate the type of a collection to the compiler, so that it can be checked. Once the compiler knows the element type of the collection, the compiler can check that you have used the collection consistently and can insert the correct casts on values being taken out of the collection.

It means that the compiler will check during compile time if you are using the correct classes and it will add any necessary cast to the generated class. For example:

//suppose the classes Region, Country, City and Customer exist
import java.util.*;
...
Map<Region,Map<Country,Map<City,List<Customer>>>> map = new HashMap<>();
Map<Country,Map<City,List<Customer>>> m = map.get(new Region());
...

You will get something like this:

//suppose the class Region exists
import java.util.*;
...
Map map = new HashMap();
Map m = (Map)map.get(new Region()); //the compiler added the cast
...


Strings in Switch
Remember the Strings in switch example presented in the last post:

//in a class
...
public void stringToNumber(String str)
{
   switch(str)
   {
      case "one":
         System.out.println(1);
         break;
      case "two":   
         System.out.println(2);
         break;   
      case "three":
         System.out.println(3);
         break;
   }
}
...

After decompiled, you will notice how is that Strings are now supported in switch statemens:

//in a class
...
public static void stringInSwitch(String str)
{
        String s = str;
        byte byte0 = -1;
        switch(s.hashCode())
        {
        case 110182: 
            if(s.equals("one"))
                byte0 = 0;
            break;

        case 115276: 
            if(s.equals("two"))
                byte0 = 1;
            break;

        case 110339486: 
            if(s.equals("three"))
                byte0 = 2;
            break;
        }
        switch(byte0)
        {
        case 0: // '\0'
            System.out.println(1);
            break;

        case 1: // '\001'
            System.out.println(2);
            break;

        case 2: // '\002'
            System.out.println(3);
            break;
        }
}
...

Yes... it's a lilttle trick. It's not like Strings are supported in switch statements directly, but their hashCodes are (hashCodes are integers). Looking at the code, I realize that it's better not to abuse using Strings in switch statements, because at the end, you get two switch statements...


Try with resources and Multi Catch
Remember the try with resources and multicatch example from the last post:

//in a class
import java.nio.file.*;
import java.io.*;
...
public static void copyFile(String src) 
              throws IOException, NullPointerException 
{
        Path path = FileSystems.getDefault().getPath(src);
        
        try (FileOutputStream fout = 
                        new FileOutputStream("file.dat")) {
            Files.copy(path, fout);
        } catch (NullPointerException | IOException ex) {
            ex.printStackTrace();
            throw ex;
        }
}
...

This example uses try with resources and multicatch all in one example. When I tried to decompile the generated class using JAD Java Decompiler, I got a lot of erros about the nested try statements, therefore I decided to try JD Java Decompiler and here is the result:

//in a class
import java.nio.file.*;
import java.io.*;
...
public static void copyFile(String src) throws IOException, NullPointerException
{
    Path path = 
         FileSystems.getDefault().getPath(src, new String[0]);
    try 
    {
      FileOutputStream fout = new FileOutputStream("file.dat"); 
      Throwable localThrowable2 = null;
      try 
      { 
        Files.copy(path, fout);
      }
      catch (Throwable localThrowable1)
      {
        localThrowable2 = localThrowable1; 
        throw localThrowable1;
      } 
      finally 
      {
        if (fout != null) 
        { //I added this { symbol for readability

          if (localThrowable2 != null) 
          { //I added this { symbol for readability

            try 
            { 
              fout.close(); 
            } 
            catch (Throwable x2) 
            { 
              localThrowable2.addSuppressed(x2); 
            } 

          } //I added this } symbol for readability
          else 
          { //I added this { symbol for readability

              fout.close();  

          } //I added this } symbol for readability

        } //I added this } symbol for readability
      }
    } 
    catch (IOException ex) 
    {
      ex.printStackTrace();
      throw ex;
    }
}
...

From the last piece of code, we can see how the compiler makes sure that any exception thrown during the copy process is not lost using the new (JDK 7) method +addSuppressed(Throwable):void of the class Throwable. This is important, because you will need all the possible exceptions when looking for a bug in your application. Also, notice that all the close operations are done in a finally statement ensuring that the resources are always closed at the end of the process.


Binary literals and underscores in literals
I think you can figure out what we'll get after decompiling this last feature...

//in a class
...
public static void coin() 
{
   int binaryNum = 0b10; //This is number 2 in binary code

   double value = 1000000000; //hard to read?
   double value = 1_000_000_000; //Easy to read with Java 7

   double value = 0b101010110111; //hard to read?
   double value = 0b1010_1011_0111; //Easy to read with Java 7

   double pi = 3.14_15_92; //another example of readability
}
...

Yep, nothing new... the compiler just rewrites the values without the underscores and translates the binary values to integer values:

//in a class
...
public static void coin()
{
        int binaryNum = 2;
        double value1 = 1000000000D;
        double value2 = 1000000000D;
        double value3 = 2743D;
        double value4 = 2743D;
        double pi = 3.1415920000000002D;
}
... 



Ok, that's all for this post. Hope you all like the new features of Project Coin (JSR334) in Java 7. There are more improvements to come in Project Coin II in Java 8 and we'll check them all in future posts.

see ya!


References:

Generics. Oracle [online].
Available on Internet: http://docs.oracle.com/javase/1.5.0/docs/guide/language/generics.html
[accessed on January 28 2012].

Overview of JDK 7 Support in NetBeans IDE. NetBeans [online].
Available on Internet: http://netbeans.org/kb/docs/java/javase-jdk7.html
[accessed on January 22 2012].

JAD Java Decompiler. Tomas Varaneckas [online].
Available on Internet: http://www.varaneckas.com/jad
[accessed on January 22 2012].

JD Java Decompiler. [online].
Available on Internet: http://java.decompiler.free.fr/
[accessed on January 31 2012].