About us  |   Contact us Web testing with JBlitz  |   Search
Java 5 news
- Website Load Test Tools - HTTP Resources - Java Technology -

A quick tour round Java 5 - Sun's sixth major release of Java - also known as 'Tiger'...

 
JBlitz Pro 5.1:
 Product home page
 Quick download
 Product overview
 Step by step guide
 Customization API
 FAQ
 Help

JBlitz Direct 4.2:
 Product home page
 Quick download

Software Store:
 Buy online now

In September 2004, Sun Microsystems released a major new version of their Java technology set, Java 5. Nicknamed 'Tiger', this release represents a real push by Sun to broaden the appeal of Java for the everyday developer and to put a fly in the Microsoft desktop application empire ointment (so to speak).

The prior release of Java, J2SE 1.4.2 made significant strides towards addressing the perceived bloated runtime footprint of Java (by providing significant performance enhancements) and the failings of the class library API (by fixing many of the historic bugs and also by enriching the class library API). Because of this, this newest release has been more squarely targeted at easing the burden of developing Java applications, applets and server side components. This has been achieved by altering the Java language itself:

We've highlighted six of the main language enhancements to illustrate the significant step forward that Java 5 represents for the Java language and the developers that use it:

  1. Generics - C++ templates come to Java
  2. Enhanced for loop - a new form of the for loop that's simpler and neater
  3. Autoboxing / unboxing - under the hood conversions between primitive types and their object equivalents
  4. Type safe enumerations - the well known enum pattern now implemented with a single line of code
  5. Static imports - a handy way of simplifying code and avoiding interfaces full of constants
  6. Variable argument lists - printf comes to Java

Generics

Easily the most awaited language enhancement, generics allow the programmer to manipulate objects in a more type-safe manner and help avoid the dreaded cast.

The simplest and most common examples for using generics are found within the java.util collection classes. These classes have been enhanced to define generic versions of each container class - lists, sets and maps etc. Thus previously:

               
  List l = new LinkedList();
  l.add(new Shape("square"));
  l.add(new Shape("circle"));
  Iterator iter = l.iterator();
  while(iter.hasNext()) {
      // Do something useful with your shape...
      Shape shape = (Shape)iter.next();
      System.out.println("Shape " + shape);
  }

Now you can use generics to write the above much more concisely, safely and without any need for casting:

               
  List<Shape> l = new LinkedList<Shape>();
  l.add(new Shape("square"));
  l.add(new Shape("circle"));
  for(Shape shape : l) {
      // Do something with your shape...
      System.out.println("Shape: " + shape);
  }

Not only is the code neater and easier to understand, it is much safer to use too. In the latter code segment, the compiler will not allow you to add anything other than a Shape to the list, or retrieve anything other than a Shape from it either.

Generics are very similar in behaviour and usage to templates in C++. They're best used in situations where the generic class can handle the subject class without needing to know anything about its behaviour. Thus, generics are ideal for container classes, pools, caches and the like.


Enhanced for loop

The enhanced for loop is a relatively minor language enhancement that simplifies the traditional for loop by removing the need to reference iterators or index variables.

It takes the form:

               
  for(<variable definition> : <expression>)

Here, before the colon, <variable definition> defines the loop variable that will hold the array or collection value used within the loop. After the colon, <expression> is an expression that evaluates to an array or collection of the correct type for the loop variable.

The easiest way to illustrate this is with a simple example. Previously, the typical means of looping through an array would involve some code similar to the following:

               
  String[] values = new String[] {"Red", "Green", "Blue"};
  for(int i = 0; i < values.length; i++) {
      System.out.println("Colour " + values[i]);
  }

Now, the equivalent loop code looks like this:

               
  String[] values = new String[] {"Red", "Green", "Blue"};
  for(String s : values) {
      System.out.println("Colour " + s);
  }

You'll notice the new version is more concise. You don't have to worry about getting the bounds of the loop right and you don't have to declare an index variable either. Note, however, that this additional 'new' version of the for loop cannot replace all occurrences of the 'old' for loop.

  • 'old' for loops can be more complex than the simple example shown above (they might traverse backwards or skip every other element etc).
  • 'old' for loops might make specific use of the index variable within the loop - in the new form it is not available.

On the up side, the new form can be applied to both arrays and collections seamlessly. In the expression for(<variable definition> : <expression>), the expression part can either be an instance of an array of an object that implements the Iterable interface. In Java 5 onwards, all java.util collection classes implement this interface, and so the 'new' form of the for loop can be used instead of an Iterator to iterate through any collection. This also gives developers a hook by making their classes Iterable, they can make them available for use within new forms of the for loop.

Here's an example using the java.util.LinkedHashSet.

               
  Collection<Integer> scores = new LinkedHashSet<Integer>();
  scores.add(99);  // Use of auto boxing
  scores.add(88);
  scores.add(77);
  for(Integer score : scores) {
      System.out.println("This score is " + score);
  }


Autoboxing / unboxing

Autoboxing / unboxing is the automated 'under the covers' conversion between primitive types and their equivalent object types. For example, the conversion between an int primitive and an Integer object or between a boolean primitive and a Boolean object.

Autoboxing / unboxing does nothing to address the fundamental distinction between objects and primitives within the Java language. It does, however help with the sorts of lines of code that developers find themselves churning out on a daily basis. It essentially lends a convenient shorthand notation for converting between primitive and object types. For instance, a trivial example to calculate squares of primes would have looked like this:

               
  List primes = new LinkedList();
  primes.add(new Integer(1));
  primes.add(new Integer(2));
  primes.add(new Integer(3));
  primes.add(new Integer(5));
  primes.add(new Integer(7));
  primes.add(new Integer(11));
  Iterator iter = primes.iterator();
  while(iter.hasNext()) {
      int prime = ((Integer)iter.next()).intValue();
      System.out.println(prime * prime);
  }

A large part of the intention of the code is hidden behind object construction (boxing) and primitive extraction (unboxing - via the method intValue()). Now, we can rewrite with the same intent as follows:

               
  List<Integer> primes = new LinkedList<Integer>();
  primes.add(1);
  primes.add(2);
  primes.add(3);
  primes.add(5);
  primes.add(7);
  primes.add(11);
  for(Integer prime : primes) {
      System.out.println(prime * prime);
  }

Much neater. 

As mentioned before, autoboxing and unboxing is just an 'under the covers' conversion between primitive types and their object equivalents. In this regard, you should take care to continue to use primitive types when appropriate and their object equivalents also when appropriate. We've listed a few fundamentals that might help you decide when to use what form:

  • Primitive types are typically fast and lightweight - they have a small memory footprint. Object equivalents are heavyweight and can burden the VM if created in large numbers.
  • Primitive types are not objects - they are not polymorphic and cannot exhibit any object behaviour. Object equivalents behave polymorphically (the numeric ones form an inheritance hierarchy derived from java.lang.Number) and exhibit useful behaviour - e.g. Integer.compareTo(Integer).
  • Primitive types are passed by value into methods. Object equivalents are passed by reference.
  • Primitive types are not reference counted or garbage collected. Object equivalents are.
  • Primitive types are mutable. Their object equivalent classes are all immutable.


Type-safe enumerations

The type-safe enumeration is basically an incorporation within the language of the well known 'type safe enum' design pattern proposed by Joshua Bloch. This pattern went something like this:

               
  public class Language {
      private String name;

      private Language(String name) {
          this.name = name;
      }

      public static final Language ENGLISH = new Language("English");
      public static final Language FRENCH = new Language("French");
      public static final Language SPANISH = new Language("Spanish");

      public String toString() {
          return name;
      }
  }

Here, you'll notice that the constructor is private and hence the only means that an external class has of getting a reference to a Language object is by using one of the predefined static final Languages. Hence, there is no chance of any Language references referring to an invalid object (this being the principal weakness of just using static final ints).

Now the type-safe enumeration support in Tiger entrenches the above pattern within the Java language itself and, in doing so, gives you a little extra hand too:

               
  public enum Language {English, French, Spanish};

This declares a fully-fledged class Language and provides three valid instances English, French and Spanish as static members. You can manipulate the instances as you might expect - for instance:

               
  Language l1 = Language.English;
  Language l2 = Language.French;        
  if(l1.equals(l2)) {
      System.out.println(l1 + " is " + l2);
  } else {
      System.out.println(l1 + " is NOT " + l2);
  }

Naturally, you cannot create your own instances of enum types:

               
  // The following is invalid - enum types cannot be instantiated
  Language l = new Language();

Enum classes also come bundled with the following functionality to round out their usefulness. They:

  • declare the method values() that returns an array containing the valid enum instances
  • declare the method valueOf(String) that returns the appropriate enum instance for the given string rendition
  • implement Comparable and Serializable
  • override the methods toString(), equals(), hashCode() and compareTo() as you'd expect.


Static imports

This feature is a rounding out of the language with the following aims:

  1. to make it easier to import static members from another class. For example, PI is a static member of the Math class. Using this constant in your code previously required the code to reference Math.PI. Now, however, you can do the following:

                   
      //  - EITHER - 
      import static java.lang.Math.PI;
      //  - OR - 
      import static java.lang.Math.*;

    Then your code simply references PI by itself:

                   
      double quarterCircle = PI / 2;
      System.out.println("Quarter circle is " + quarterCircle + " radians");

  2. to remove the dreaded Constant Interface Antipattern. This is the old pattern much touted in the distant past (when Java was all new and shiny) that involved making up an interface, sticking a whole load of constants inside it and making your classes implement it solely so that they could reference the constants within. This, we can safely say, is not what interfaces were designed to do. The principal function of an interface is to expose the behaviour of a class or classes whilst hiding the implementation detail.

    Anyway, with import static, the need to create pseudo interfaces that are solely used as holders for constants is lessened. Such constants that are not part of the public API can be safely left within implementation classes and imported more easily the new way.


Variable argument lists

Variable argument lists are a staple feature of C and C++ (usually referred to as varargs). The archetypal example vararg method in 'C' and 'C++' is printf. Java 5 now embraces this dubious language construct by introducing its own version.

As an example, take the method getMethod() within the class java.lang.Class. This method formerly had the following signature:

               
  public Method getMethod(String name, Class[] parameterTypes)

Now the self same method has the following signature:

               
  public Method getMethod(String name, Class... parameterTypes)

This states that the method parameters are a String object and a variable argument list of Class objects. A few points of note:

  • The new method signature is 'upward compatible' with pre-existing APIs - the Java 5 runtime is happy to execute a pre Java 5 application that is coded to the old signature.
  • The receiving method is passed the varargs parameter as an array of the declared type. (in the example above, parameterTypes has the type Class[])
  • Invocations of the varargs method can pass in zero, one or more arguments for the variable argument list. If zero arguments are passed (in the example above, getMethod("myMethod"), an empty array is passed to the receiving method.
  • Invocations of the varargs method can also pass in an array of the correct type for the variable argument list. This is how pre Java 5 code executes the method.
  • Variable argument lists can only be used in the last position of a method's parameter list.

This new language feature can be neatly used with the autoboxing language feature to make calling code significantly easier to write and understand. A trivial example: using a method sumMe() as follows:

               
  public void sumMe(Number... numbers) {
      double result = 0;
      for(Number num : numbers) {
          result += num.doubleValue();
      }
      System.out.println("Total is " + result);
  }

We can write invocation code that passes in all sorts of unboxed numbers:

               
  sumMe(0xFF, Math.PI, 1, -42, 777l);

Producing the predictable result 994.14159265358978.

Java 5 news
Copyright © 2001-2007 Clan Productions Limited