Java 101: The Next Generation

Java 101: The essential Java language features tour, Part 3

Autoboxing and unboxing, enhanced for loops, static imports, varargs, and covariant return types

Java 101: The Next Generation

Show More
1 2 Page 2
Page 2 of 2

To satisfy the need for constant interfaces while avoiding the problems imposed by the use of these interfaces, Java 5 introduced static imports. This language feature can be used to import a class's static members. It's implemented via the import static statement whose syntax appears below:

import static packagespec . classname . (
staticmembername | * );

This statement's placement of static after import distinguishes it from a regular import statement. Its syntax is similar to the regular import statement in terms of the standard period-separated list of package and subpackage names. Either a single static member name or all static member names (thanks to the asterisk) can be imported. Consider the following examples:

import static java.lang.Math.*;   // Import all static members from Math.
import static java.lang.Math.PI;  // Import the PI static constant only.
import static java.lang.Math.cos; // Import the cos() static method only.

Once imported, static members can be specified without having to prefix them with their class names. For example, after specifying either the first or third static import, you could specify cos directly, as in double cosine = cos(angle);.

To fix Listing 6 so that it no longer relies on implements Switchable, we can insert a static import, which Listing 7 demonstrates.

Listing 7. Light.java (version 2)

package foo;

import static foo.Switchable.*;

public class Light
{
   private boolean state = OFF;

   public void printState()
   {
      System.out.printf("state = %s%n", (state == OFF) ? "OFF" : "ON");
   }

   public void toggle()
   {
      state = (state == OFF) ? ON : OFF;
   }
}

Listing 7 begins with a package foo; statement because you cannot import static members from a type located in the default package. This package name appears as part of the subsequent import static foo.Switchable.*; static import.

There are two additional cautions in regard to static imports:

  • When two static imports import the same named member, the compiler reports an error. For example, suppose package physics contains a Math class that's identical to java.lang's Math class in that it implements the same PI constant and trigonometric methods. When confronted by the following code fragment, the compiler reports errors because it cannot determine whether java.lang.Math's or physics.Math's PI constant is being accessed and cos() method is being called:

    import static java.lang.Math.cos;
    import static physics.Math.cos;
    
    double angle = PI;
    System.out.println(cos(angle));
    
  • Overuse of static imports can make your code unreadable and unmaintainable because it pollutes the code's namespace with all of the static members you import. Also, anyone reading your code could have a hard time finding out which class a static member comes from, especially when importing all static member names from a class.

Varargs

The variable arguments (varargs) feature lets you pass a variable list of arguments to a method or constructor without having to first box those arguments into an array.

To use varargs, you must first declare the method or constructor with ... after the rightmost parameter type name in the method's/constructor's parameter list. Consider the following example:

static void printNames(String... names)
{
   for (String name: names)
      System.out.println(name);
}

In this example, printNames() declares a single names parameter. Note the three dots after the String type name. Also, I use the enhanced for loop to iterate over the variable number of String arguments passed to this method.

When invoking the method, specify a comma-separated list of arguments that match the parameter type. For example, given the previous method declaration, you could invoke this method as follows:

printNames("Java", "JRuby", "Jython", "Scala");

Behind the scenes, varargs is implemented in terms of an array and ... is syntax sugar that hides the array implementation. However, you can access the array from within the method, as follows:

static void printNames2(String... names)
{
   for (int i = 0; i < names.length; i++)
      System.out.println(names[i]);
}

Also, you can create and pass the array directly, although you would typically not do so. Check out the example below:

printNames2(new String[] { "Java", "JRuby", "Jython", "Scala" });

Covariant return types

Java 5's final new language feature is the covariant return type, which is a method return type that, in the superclass's method declaration, is the supertype of the return type in the subclass's overriding method declaration. Consider Listing 8.

Listing 8. CovarDemo.java

class Paper
{
   @Override
   public String toString()
   {
      return "paper instance";
   }
}

class PaperFactory
{
   Paper newInstance()
   {
      return new Paper();
   }
}

class Cardboard extends Paper
{
   @Override
   public String toString()
   {
      return "cardboard instance";
   }
}

class CardboardFactory extends PaperFactory
{
   @Override
   Cardboard newInstance()
   {
      return new Cardboard();
   }
}

public class CovarDemo
{
   public static void main(String[] args)
   {
      PaperFactory pf = new PaperFactory();
      Paper paper = pf.newInstance();
      System.out.println(paper);
      CardboardFactory cf = new CardboardFactory();
      Cardboard cardboard = cf.newInstance();
      System.out.println(cardboard);
   }
}

Listing 8 presents Paper, PaperFactory, Cardboard, CardboardFactory, and CovarDemo classes. The key classes in this demo are PaperFactory and CardboardFactory.

PaperFactory declares a newInstance() method that CardboardFactory overrides. Notice that the return type changes from Paper in the PaperFactory superclass to Cardboard in the CardboardFactory subclass. The return type is said to be covariant.

Compile Listing 8 (javac CovarDemo.java) and run the application (java CovarDemo). You should observe the following output:

paper instance
cardboard instance

Covariant return types eliminate explicit casts. For example, if CardboardFactory's newInstance() method had been declared with a Paper return type, you would have to specify Cardboard cardboard = (Cardboard) cf.newInstance(); in the main() method.

In conclusion

Java 5 improved type safety mainly through generics but also through typesafe enums. It also improved developer productivity by introducing annotations, autoboxing and unboxing, the enhanced for loop, static imports, varargs, and covariant return types. Next up in the Java 101: The next generation series is a look at productivity-oriented language features introduced in Java 7.

1 2 Page 2
Page 2 of 2