Java 101: The Next Generation

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

Java programming with assertions and generics

Java 101: The Next Generation

Show More
1 2 3 Page 3
Page 3 of 3

You can provide an upper bound for a wildcard by specifying extends followed by a type name. Similarly, you can supply a lower bound for a wildcard by specifying super followed by a type name. These bounds limit the types that can be passed as actual type arguments.

In the example, you can interpret ? extends String as any actual type argument that happens to be String or a subclass. Similarly, you can interpret ? super String as any actual type argument that happens to be String or a superclass. Because String is final, which means that it cannot be extended, only source lists of String objects and destination lists of String or Object objects can be passed, which isn’t very useful.

You can fully solve this problem by using a generic method, which is a class or instance method with a type-generalized implementation. A generic method declaration adheres to the following syntax:

<formalTypeParameterList> returnType
identifier(parameterList)

A generic method’s formal type parameter list precedes its return type. It consists of type parameters and optional upper bounds. A type parameter can be used as the return type and can appear in the parameter list.

Listing 9 demonstrates how to declare and invoke a generic copy() method.

Listing 9. GenDemo.java (version 5)

import java.util.ArrayList;
import java.util.List;

public class GenDemo
{
   public static void main(String[] args)
   {
      List<Integer> grades = new ArrayList<Integer>();
      Integer[] gradeValues =
      {
         new Integer(96),
         new Integer(95),
         new Integer(27),
         new Integer(100),
         new Integer(43),
         new Integer(68)
      };
      for (int i = 0; i < gradeValues.length; i++)
         grades.add(gradeValues[i]);
      List<Integer> failedGrades = new ArrayList<Integer>();
      copy(grades, failedGrades, new Filter<Integer>()
                                 {
                                    public boolean accept(Integer grade)
                                    {
                                       return grade.intValue() <= 50;
                                    }
                                 });
      for (int i = 0; i < failedGrades.size(); i++)
         System.out.println(failedGrades.get(i));
   }

   static <T> void copy(List<T> src, List<T> dest,
Filter<T> filter)
   {
      for (int i = 0; i < src.size(); i++)
         if (filter.accept(src.get(i)))
            dest.add(src.get(i));
   }
}

interface Filter<T>
{
   boolean accept(T o);
}

In Listing 9 I’ve declared a <T> void copy(List<T> src, List<T> dest, Filter<T> filter) generic method. The compiler notes that the type of each of the src, dest, and filter parameters includes the type parameter T. This means that the same actual type argument must be passed during a method invocation, and the compiler infers this argument by examining the invocation.

If you compile Listing 9 (javac GenDemo.java) and run the application (java GenDemo) you should observe the following output:

27
43

What’s controversial about generics in the Java language?

While generics as such might not be controversial, their particular implementation in the Java language has been. Generics were implemented as a compile-time feature that amounts to syntactic sugar for eliminating casts. The compiler throws away a generic type or generic method’s formal type parameter list after compiling the source code. This “throwing away” behavior is known as erasure. Other examples of erasure in generics include inserting casts to the appropriate types when code isn’t type correct, and replacing type parameters by their upper bounds (such as Object).

More generics controversy

Generics are controversial for reasons other than erasure. See the StackOverflow.com topic “Why do some claim that Java’s implementation of generics is bad?” for more discussion, including the suggestion that wildcards can be confusing and the fact that generics do not support value types (for instance, you cannot specify List<int>).

Using erasure leads to several limitations:

  • With one exception, the instanceof operator cannot be used with parameterized types. The exception is an unbounded wildcard. For example, you cannot specify Set<Shape> shapes = null; if (shapes instanceof ArrayList<Shape>) {}. Instead, you need to change the instanceof expression to shapes instanceof ArrayList<?>, which demonstrates an unbounded wildcard. Alternatively, you could specify shapes instanceof ArrayList, which demonstrates a raw type (and which is the preferred use).
  • The compiler transforms generic code into non-generic code, which is stored in the classfile. Some developers have pointed out that erasure prevents you from using reflection to obtain generics information, which isn’t present in the classfile. In “Java Reflection: Generics” developer Jakob Jenkov points out a few cases where generics information is stored in a classfile, and this information can be accessed reflectively.
  • You cannot use type parameters in array-creation expressions; for example elements = new E[size];. The compiler will report a generic array creation error message if you try to do so.

Given erasure’s limitations, you might wonder why generics were implemented with erasure. The reason is simple: the Java compiler was refactored to use erasure so that generic code could interoperate with legacy Java code, which isn’t generic. Without that backward compatibility, legacy Java code would fail to compile in a Java compiler supporting generics.

Conclusion to Part 1

The Java language has evolved through the addition of many new features. In this article I’ve shown you how to use assertions to increase your confidence in the correctness of your code, and how to use generics to eliminate ClassCastExceptions. By using assertions and generics, you can craft code that’s much more reliable and minimize code failure at runtime, as well as the associated headache of dealing with angry clients.

Java 5 was a pivotal release in the history of the Java platform, and while generics were more controversial than some other features, they weren’t necessarily more important. My next article will introduce two more essential features added to the language in Java 5: typesafe enums and annotations. Until then, check out the source code for this article, which contains more tips and examples for programming with assertions and generics.

Jeff Friesen is a freelance tutor and software developer with an emphasis on Java and Android. In addition to writing Java and Android books for Apress, Jeff has written numerous articles on Java and other technologies for JavaWorld, informIT, Java.net, DevSource, and SitePoint. Jeff can be contacted via his website at TutorTutor.ca.

Learn more about this topic

  • Download the source code for this article.
  • Read Angelika Langer’s Java Generics FAQs for a wealth of information and perspective about generics in the Java language.
  • For students of the Java language and its controversies, Langer’s “Und erstanding the closures debate” (JavaWorld, June 2008) compares the three initial proposals for adding closures, or lambda expressions, to the Java language in Java 7.
  • See “Java Reflection: Generics” (Jakob Jenkov, Jenkov.com) for further discussion about reflection with generics and special cases where it is possible to access generics information at runtime.
  • More from Java 101: The next generation:
  • More about the Java Collections Framework on JavaWorld:
1 2 3 Page 3
Page 3 of 3