15-200 Lecture 1 (Friday, September 8, 2006)

Java 5 Update

This year, we're using Java 1.5 -- so we're going to spend today talking about a few of the things that are new. As it turns out, a lot is new, actually. So, we're just going to hit the high points.

The following links might be useful to you:

Generics

Sometimes we write code that is heavily algorithmic. We describe manipulations that can be applied to many different data structures. Sorting is an example of this type of algorithmic code. Given a function that can compare the objects, the same manipulations can be used to sort objects of any type.

When we write this type of code in Java, we often make use of Interfaces. In the case of sorting, we make use of, for example, the Comparable interface. But, this isn't a complete solution. It requires that the class implement some common interface. It doesn't allow for us, for example, to pass in function-objects that manipulate it. Of course, we can pull this off by using things such as Comparators. But, we hit a roadblock when it comes to primitives.

It would be nice if we had a way to descirbe the algorithm and then let the type get plugged in after-the-fact. And, that is exactly what Java's generics mechanism does for us. It allows us to implement an algorithm in Java, but use a placeholder paramter, almost like a configuration constant, for the type. Then, when we actually go to use the generic class, we just plug in the type and off we go.

Generics aren't new. C++ has "templates". Java's "generics" look a lot like templates. The big difference is that templates are instantiated before compiling, whereas Java's generics are actually processed by the compiler. In C++, templates are almost entirely "cookie cutter code" -- mechanical substitutions. In Java, because of the type system, life is a bit more complicated. Java needs to insert some bridge code to do runtime type checking.

Generic Collections

Java's Collections package has been rewritten using generics. This means that it is no longer necessary to cast from Object when accessing items within a collection -- it is possible to create a Collection of the proper type.

Let's take a quick look. The following code is basically Java 1.4 code. It uses the Collection class, but doesn't provide any of the new type information, so it looks exactly the same

  import java.util.*;

  class GenericsExample {

    public static void main(String[] args) {

      List l = new LinkedList();

      l.add ("Hello");
      l.add ("Great");
      l.add("World");

      System.out.println (l);
      System.out.println ((String) l.get(0));

    }
  }
  

And, it will compile and run under 1.5. But, it'll produce a warning:

  Note: GenericsExample.java uses unchecked or unsafe operations.
  Note: Recompile with -Xlint:unchecked for details.
  

This warning is basically telling us that it is defaulting to Object so it can't do any type checking for us.

Take a look at the version below. Notice the type information provided within the <aligator-brackets>. This provides a type to use in place of the default Object. Specifically, this example makes the list a list of Strings instead of a list of Objects. This is really nice -- notice that the ugly-but-usual cast from Object to String is missing on the get().

  import java.util.*;

  class GenericsExample {

    public static void main(String[] args) {

      List<String> l = new LinkedList<String>();

      l.add ("Hello");
      l.add ("Great");
      l.add("World");

      System.out.println (l);
      System.out.println (l.get(0));
    }
  }
  

The for-each loop

Java 5 also introduced a new construct for traversing collections: the for-each loop. The for-each loop isn't really a new first-class construct. Instead you can think of it as a really convenient syntax for traversing a data structure using an interator.

Consider the example below. Notice the :-colon. When reading the code, read the :-colon as "in".

  import java.util.*;

  class GenericsExample {


    public static void main(String[] args) {

      List l = new LinkedList();

      l.add ("Hello");
      l.add ("Great");
      l.add("World");

      System.out.println (l);
      System.out.println ((String) l.get(0));


      for (Object word : l ) {
        System.out.println ((String)word);
      }

    }
  }
  

The example above printes each and every item within list l. See how pretty? The for-each loop can basically be used any time you are doing simple traversal. It can't be used when you are mutating the collection within the loop nor can for-each loops be nested. When it comes right down to it, its just a simple "macro" rewritten into standard syntax before the byte code is emitted.

Autoboxing

In Java 1.4 it was a pain to use primitives within collections or in other contexts where types of Objects were expected. We had to wrap the primitive into a Integer, Long, Double, Float, Character, Boolean, or the like, first. Then, after removing it from the data structure, we had to "unwrap" to get back at the primitive: intValue(), doubleValue(), longValue(), &c. And, although equivalent in the abstract, the same operations didn't apply to each. For example, the mathematical operators weren't defined on Integer, Double, Long, Float, &c.

Java 1.5 resovles this with "autoboxing". Basically, any time a numerical operator is used on an Object (wrapped primitive) or there would otherwise be a type mismatch, the compiler will automatically insert the code to wrap the primitive value or unwrap the Object, as appropriate. Please consider the examples below:

  Integer bigI = 5; // 5 gets wraped
  bigI = bigI + 3; // I gets unwrapped, the add happens, and then wrapped again
  
  int i = 2;
  LinkedList linkedList = new LinkedList();
  linkedList.add(i); // i is wrapped

  i = linkedList.get(0); // the Integer returned is unwrapped
  

Now let's consider the example below. Autoboxing won't help us. Can you guess why?

  int i = 5;

  i.toString(); // No autoboxing. I stays an int and this doesn't compile
  

The problem in the example above is that Java has no idea that it should be boxing i. It would need to know that i should be an Integer before looking at the Integer class to find the toString() method. How does it know that toString() isn't the Long's toString()? Or the Double's?

Remember, java will only box or unbox if it would otherwise recognize a type compatibility problem or if there is a mathematical operator being applied to a wrapped type.