Intro to Java Generics

This entry is part 8 of 13 in the series Intro to Java

To me it seems a better word would be Specifics. The use of Generics is applying a specific type to a class that can be used in a generic way. One of the most common generic classes is List. A list allows you to put any type of Object in to it. In fact, you can put several different types of Objects into a List. But, there is a way to create a specific List type that can only take one type of Object.

What do they look like?

Since we are talking about Lists, lets look at what a ArrayList would look like that could only take Strings.

List aList = new ArrayList();

What goes between the <> is the type that you want to specify for the List. You need to do it in the left side declaration, and on the right side where you are creating the Object.

Does this work for all classes?

A class has to be specifically defined for generics. All the collections have been defined this way, and you can create your own classes like this too.

Lets look at the class definition of List:

public interface List extends Collection {

Now lets look at methods such as add and get:

boolean add(E o);

E get(int index);

In the definition, between the <> List is defined to have a variable E that will contain a class type. (It is always a class type.) This variable can be used anywhere you want to specify this class type. In the case of the add() method, it means that the add method can only accept Objects of the class type E. For the get method, it means that the get method returns an Object of type E.

How is any of this useful?

Well, first of all, if you have your compiler set up to compile at a Java 5 level, adding these types removes all the compiler warnings. In Eclipse it will remove all the yellow warnings that are annoying.

More importantly, it allows you to limit the type that you take in with your methods.

GenericsTest1 serves as an example of how you would apply generics to a List.

public class GenericsTest1 {
   public static void main(String[] args) {
      // Create a List without specifying type parameters
      List aList1 = new ArrayList();
      aList1.add(new Bird());
      aList1.add(new Fish());
      aList1.add(new Mammal());
      // Does compiler complain? Does it work at runtime?
      //aList1.add(new Object());

      for (Iterator iterator = aList1.iterator(); iterator.hasNext();) {
         //notice the need for a cast
         Animal aAnimal = (Animal) iterator.next();
         aAnimal.move();
      }

      // Create a List and specify that only an Animal can be used.
      List aList2 = new ArrayList();
      aList2.add(new Bird());
      aList2.add(new Fish());
      aList2.add(new Mammal());
      // Does compiler complain? Does it work at runtime?
      //aList2.add(new Object());

      for (Iterator iterator = aList2.iterator(); iterator.hasNext();) {
         // notice no casting
         Animal aAnimal = iterator.next();
         aAnimal.move();
      }
   }
}

GenericsTest2 shows a similar example, but using a Map.

public class GenericsTest2 {
   public static void main(String[] args) {
      Integer aIntegerKey = new Integer(10);

      // Create a Map without specifying type parameters
      Map aMap1 = new HashMap();
      aMap1.put("bird", new Bird());
      aMap1.put("fish", new Fish());
      // Notice that there is no restriction on the key type
      aMap1.put(aIntegerKey, new Mammal());

      Animal aBird = (Animal) aMap1.get("bird");
      aBird.move();

      Animal aFish = (Animal) aMap1.get("fish");
      aFish.move();

      Animal aMammal = (Animal) aMap1.get(aIntegerKey);
      aMammal.move();

      // Create a Map an specify that only a String key and an Animal value
      // can be used in the map.
      Map aMap2 = new HashMap();
      aMap2.put("bird", new Bird());
      aMap2.put("fish", new Fish());

      // What happens when this is uncommented
      //aMap2.put(aIntegerKey, new Mammal());

      Animal aBird2 = aMap2.get("bird");
      aBird2.move();

      Animal aFish2 = aMap2.get("fish");
      aFish2.move();

      //What happens when this is uncommented. Compiler? Run time? Why?
      //Animal aMammal2 = aMap2.get(aIntegerKey);
      //aMammal2.move();
   }
}

Now lets rewrite a couple of things to use generics.

Look at the CollectionUtilities.printAnimalList() method from the “Intro to Java Collections” lesson.

public static void printAnimalList(List pAnimalList) {
   for (Iterator iterator = pAnimalList.iterator(); iterator.hasNext();) {
      // notice the need for a cast
      Animal aAnimal = (Animal) iterator.next();
      System.out.println(aAnimal.getName() + ":" + aAnimal.getType());
   }
}

You can’t get much more un type safe than this. We define the method to only take in a List. But in the code we expect to only have Animals. We also have to cast to an Animal because when the List is created we don’t define a specific type, so the list can contain any type of Object.

Lets rewrite this method using generics.

public static void printAnimalList(List pAnimalList) {
   for (Iterator iterator = pAnimalList.iterator(); iterator.hasNext();) {
      // notice no casting
      Animal aAnimal = iterator.next();
      System.out.println(aAnimal.getName() + ":" + aAnimal.getType());
   }
}

Our method definition has determined that only Lists that contain Animal types are allowed. If someone tried to use this method with a List of Strings, the compiler would not let it happen. Also notice that we no longer need to cast from this Iterator. That is because the .iterator() method in List returns a Iterator, and Iterator doesn’t return just an Object, but one of the Animal type.

Even better, lets use a new Java 5 looping structure called a “for each” loop. This works for any class that implements the Iterable interface (like List).

public static void printAnimalList(List pAnimalList) {
   for (Animal aAnimal : pAnimalList) {
      System.out.println(aAnimal.getName() + ":" + aAnimal.getType());
   }
}

Now lets look at Cage. Cage is a version of a cage that allows you to specify what type of animal the cage contains using generics. See GenericsTest3. This is not quite the same as BirdCage, it is not a singleton, but BirdCage could also be rewritten so that it could be a cage for any animal, not just birds. Try to rewrite it using this Cage as an example.

public class Cage {
   // We can then use T to define our list class type
   private List animalList;

   public Cage() {
      // When we create the list we must tell it a type too.
      animalList = new ArrayList();
   }

   // We can even specify in a method signature that only objects of type T are
   // allowed to be here. Not just any Animal, but just of class type T.
   public void insertAnimal(T pAnimal) {
      animalList.add(pAnimal);
      System.out.println(pAnimal.getName() + " " + pAnimal.getType() + " is in the cage.");
   }

   // When we return our list, it will be a list that only contains class type
   // T, and you won't need to cast individual elements from the list.
   public List getAnimals() {
      return animalList;
   }
}
public class GenericsTest3 {
   public static void main(String[] args) {
      // Without specifying type parameters the compiler gives us all kinds of warnings.
      Cage aCage = new Cage();
      aCage.insertAnimal(new Bird());
      // Notice that because we didn't specify a type when we created the
      // Cage, we can actually add a fish.
      aCage.insertAnimal(new Fish());

      // Lets add parameters.
      Cage aBirdCage = new Cage();
      aBirdCage.insertAnimal(new Bird());
      aBirdCage.insertAnimal(new Bird());
      // What happens when this is uncommented
      // aBirdCage.insertAnimal(new Fish());

      // You can get back a non parameterized list, and it is functional, but
      // you get a warning from the compiler.
      List aList2 = aBirdCage.getAnimals();
      System.out.println(aList2.size());

      // Add the type parameter to list to make the compiler happy.
      List aBirdList = aBirdCage.getAnimals();
      System.out.println(aBirdList.size());
   }
}

Letter Conventions

There is no restriction on what letters you can use in your class definitions and method signatures. In fact, instead of “T” you could use “QQ” or “Type”. It seems that the Java authors have settled on using just one letter, and they themselves have been fairly consistent about the letters they use.

In the collections packages they usually use “E” for element. So when you look at the interface for List, you will see .

If you look at the Map interface, you will see for Key and Value.

Also, other classes that take in a class type usually use the letter “T” for type.

More Information:

Series NavigationIntro to Java CollectionsIntro to Java Annotations