Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

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

Java programming with assertions and generics

  • Print
  • Feedback

Page 8 of 9

north
south
east
west
98
63
87

Discovering generic methods

Now say you want to copy a list of objects to another list subject to a filter. You might consider declaring a void copy(List<Object> src, List<Object> dst, Filter filter) method, but this method would only be able to copy Lists of Objects and nothing else.

To pass source and destination lists of arbitrary type, you need to use the wildcard for a type placeholder. For example, consider the following copy() method:

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

This method's parameter list is correct, but there's a problem. According to the compiler, dest.add(src.get(i)); violates type safety. The ? implies that any kind of object can be the list's element type, and it's possible that the source and destination element types are incompatible.

For example, if the source list was a List of Shape and the destination list was a List of String, and copy() was allowed to proceed, ClassCastException would be thrown when attempting to retrieve the destination list's elements.

You could partially solve this problem by providing upper and lower bounds for the wildcards, as follows:

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

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:

  • Print
  • Feedback

Resources
  • 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: