Contents


Unit 20: Generics

Define classes with abstract type parameters

Comments

Before you begin

This unit is part of the "Intro to Java programming" learning path. Although the concepts discussed in the individual units are standalone in nature, the hands-on component builds as you progress through the units, and I recommend that you review the prerequisites, setup, and unit details before proceeding.

Unit objectives

  • Understand the advantages of using generics
  • Know when and how to parameterize classes or methods
  • Learn how to iterate with generics
  • Know how to use the enum data type

What are generics?

JDK 5.0 (released in 2004) introduced generic types (generics) and the associated syntax into the Java language. Basically, some then-familiar JDK classes were replaced with their generic equivalents. Generics is a compiler mechanism whereby you can create (and use) types of things (such as classes or interfaces, and methods) in a generic fashion by harvesting the common code and parameterizing (or templatizing) the rest. This approach to programming is called generic programming.

Generics in action

To see what a difference generics makes, consider the example of a class that has been in the JDK for a long time: java.util.ArrayList, which is a List of Objects that's backed by an array.

Listing 1 shows how java.util.ArrayList is instantiated.

Listing 1. Instantiating ArrayList
ArrayList arrayList = new ArrayList();
arrayList.add("A String");
arrayList.add(new Integer(10));
arrayList.add("Another String");
// So far, so good.

As you can see, the ArrayList is heterogeneous: It contains two String types and one Integer type. Before JDK 5.0, the Java language had nothing to constrain this behavior, which caused many coding mistakes. In Listing 1, for example, everything is looking good so far. But what about accessing the elements of the ArrayList, which Listing 2 tries to do?

Listing 2. Attempt to access elements in ArrayList
ArrayList arrayList = new ArrayList();
arrayList.add("A String");
arrayList.add(new Integer(10));
arrayList.add("Another String");
// So far, so good.
processArrayList(arrayList);
// In some later part of the code...
private void processArrayList(ArrayList theList) {
  for (int aa = 0; aa < theList.size(); aa++) {
    // At some point, this will fail...
    String s = (String)theList.get(aa);
  }
}

Without prior knowledge of what's in the ArrayList, you must either check the element that you want to access to see if you can handle its type, or face a possible ClassCastException.

With generics, you can specify the type of item that went in the ArrayList. Listing 3 shows how, and what happens if you try and add an object of the wrong type (line 3).

Listing 3. A second attempt, using generics
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("A String");
arrayList.add(new Integer(10));// compiler error!
arrayList.add("Another String");
// So far, so good.
processArrayList(arrayList);
// In some later part of the code...
private void processArrayList(ArrayList<String> theList) {
  for (int aa = 0; aa < theList.size(); aa++) {
    // No cast necessary...
    String s = theList.get(aa);
  }
}

Iterating with generics

Generics enhance the Java language with special syntax for dealing with entities, such as Lists, that you commonly want to step through element by element. If you want to iterate through ArrayList, for instance, you could rewrite the code from Listing 3 like so:

private void processArrayList(ArrayList<String> theList) {
  for (String s : theList) {
    String s = theList.get(aa);
  }
}

This syntax works for any type of object that is Iterable (that is, implements the Iterable interface).

Parameterized classes

Parameterized classes shine when it comes to collections, so that's the context for the following examples. Consider the List interface, which represents an ordered collection of objects. In the most common use case, you add items to the List and then access those items either by index or by iterating over the List.

If you're thinking about parameterizing a class, consider if the following criteria apply:

  • A core class is at the center of some kind of wrapper: The "thing" at the center of the class might apply widely, and the features (attributes, for example) surrounding it are identical.
  • The behavior is common: You do pretty much the same operations regardless of the "thing" at the center of the class.

Applying these two criteria, you can see that a collection fits the bill:

  • The "thing" is the class of which the collection is composed.
  • The operations (such as add, remove, size, and clear) are pretty much the same regardless of the object of which the collection is composed.

A parameterized List

In generics syntax, the code to create a List looks like this:

List<E> listReference = new concreteListClass<E>();

The E, which stands for Element, is the "thing" I mentioned earlier. The concreteListClass is the class from the JDK that you're instantiating. The JDK includes several List<E> implementations, but you use ArrayList<E>. Another way you might see a generic class discussed is Class<T>, where T stands for Type. When you see E in Java code, it's usually referring to a collection of some kind. And when you see T, it's denoting a parameterized class.

So, to create an ArrayList of, say, java.lang.Integer, you do this:

List<Integer> listOfIntegers = new ArrayList<Integer>();

SimpleList: A parameterized class

Now suppose you want to create your own parameterized class called SimpleList, with three methods:

  • add() adds an element to the end of the SimpleList.
  • size() returns the current number of elements in the SimpleList.
  • clear() completely clears the contents of the SimpleList.

Listing 4 shows the syntax to parameterize SimpleList.

Listing 4. Parameterizing SimpleList
package com.makotojava.intro;
import java.util.ArrayList;
import java.util.List;
public class SimpleList<E> {
  private List<E> backingStore;
  public SimpleList() {
    backingStore = new ArrayList<E>();
  }
  public E add(E e) {
    if (backingStore.add(e))
    return e;
    else
    return null;
  }
  public int size() {
    return backingStore.size();
  }
  public void clear() {
    backingStore.clear();
  }
}

SimpleList can be parameterized with any Object subclass. To create and use a SimpleList of, say, java.math.BigDecimal objects, you might do this:

package com.makotojava.intro;
import java.math.BigDecimal;
import java.util.logging.Logger;
import org.junit.Test;
public class SimpleListTest {
  @Test
  public void testAdd() {
    Logger log = Logger.getLogger(SimpleListTest.class.getName());
    
    SimpleList<BigDecimal> sl = new SimpleList<>();
    sl.add(BigDecimal.ONE);
    log.info("SimpleList size is : " + sl.size());
    sl.add(BigDecimal.ZERO);
    log.info("SimpleList size is : " + sl.size());
    sl.clear();
    log.info("SimpleList size is : " + sl.size());
  }
}

And you would get this output:

Sep 20, 2015 10:24:33 AM com.makotojava.intro.SimpleListTest testAdd 
INFO: SimpleList size is: 1 Sep 20, 2015 10:24:33 AM com.makotojava.intro.SimpleListTest testAdd 
INFO: SimpleList size is: 2 Sep 20, 
2015 10:24:33 AM com.makotojava.intro.SimpleListTest testAdd 
INFO: SimpleList size is: 0

Parameterized methods

At times, you might not want to parameterize your entire class, but only one or two methods. In this case, you create a generic method. Consider the example in Listing 5, where the method formatArray is used to create a string representation of the contents of an array.

Listing 5. A generic method
public class MyClass {
// Other possible stuff... ignore...
  public <E> String formatArray(E[] arrayToFormat) {
    StringBuilder sb = new StringBuilder();

    int index = 0;
    for (E element : arrayToFormat) {
      sb.append("Element ");
      sb.append(index++);
      sb.append(" => ");
      sb.append(element);
      sb.append('\n');
    }

    return sb.toString();
  }
// More possible stuff... ignore...
}

Rather than parameterize MyClass, you make generic just the one method you want to use create a consistent string representation that works for any element type.

In practice, you'll find yourself using parameterized classes and interfaces far more often then methods, but now you know that the capability is available if you need it.

enum types

In JDK 5.0, a new data type was added to the Java language, called enum (not to be confused with java.util.Enumeration). The enum type represents a set of constant objects that are all related to a particular concept, each of which represents a different constant value in that set. Before enum was introduced into the language, you would have defined a set of constant values for a concept (say, gender) like so:

public class Person {
  public static final String MALE = "male";
  public static final String FEMALE = "female";
  public static final String OTHER = "other";
}

Any code that needed to reference that constant value would have been written something like this:

public void myMethod() {
  //. . .
  String genderMale = Person.MALE;
  //. . .
}

Defining constants with enum

Using the enum type makes defining constants much more formal — and more powerful. Here's the enum definition for Gender:

public enum Gender {
  MALE,
  FEMALE,
  OTHER
}

This example only scratches the surface of what you can do with enums. In fact, enums are much like classes, so they can have constructors, attributes, and methods:

package com.makotojava.intro;

public enum Gender {
  MALE("male"),
  FEMALE("female"),
  OTHER("other");

  private String displayName;
  private Gender(String displayName) {
    this.displayName = displayName;
  }

  public String getDisplayName() {
    return this.displayName;
  }
}

One difference between a class and an enum is that an enum's constructor must be declared private, and it cannot extend (or inherit from) other enums. However, an enumcan implement an interface.

An enum implementing an interface

Suppose you define an interface, Displayable:

package com.makotojava.intro;
public interface Displayable {
  public String getDisplayName();
}

Your Gender enum could implement this interface (and any other enum that needed to produce a friendly display name), like so:

package com.makotojava.intro;

public enum Gender implements Displayable {
  MALE("male"),
  FEMALE("female"),
  OTHER("other");

  private String displayName;
  private Gender(String displayName) {
    this.displayName = displayName;
  }
  @Override
  public String getDisplayName() {
    return this.displayName;
  }
}

Test your understanding

  1. Which statement best describes generics?
    1. Generics is built into the Java language to enable generic programming.
    2. Generics is a compiler mechanism that allows types to be specified as parameters, which results in code templates to help facilitate code reuse.
    3. Generics is a programming approach to creating reusable classes in the Java language, where you specify the object's class when instantiating it.
    4. All of the above.
  2. Which of the following for loop statement snippets correctly uses the new generics compiler shorthand for accessing the following String[]?
    String[] stringArray = { "1", "2", "3", "Four" };
    1. for (int a = 0; a < stringArray.length)
    2. for (int a = 0; a < stringArray.length; a++)
    3. for (String s : stringArray)
    4. for (stringArray[s] = a)
    5. None of the above
  3. The enum type is best described as:
    1. A new compiler shortcut to define constants, rather than declaring static final Strings.
    2. A new data type that can have attributes, constructors, and even methods, specifically designed for defining static values that have a type that can be checked by the compiler.
    3. A new data type designed to creating numeric constants.
    4. Enums do not exist in Java.
    5. None of the above.
  4. True or false: The Java List interface is an example of generics in the JDK.
  5. True or false: Enums cannot implement an interface.
  6. Define a new enum called EyeColor to represent the following eye colors:
    • Blue
    • Green
    • Brown
    • Gray
    • Gold
    • Black
    Your enum should have a single constructor that takes a String description of the eye color. Include a method called getDescription() to retrieve the description.
  7. Implement the MyClass from Listing 5, and create a JUnit test case (just to act as a test harness) to format a String array of four elements (whatever String values you like). Print the results using a JDK Logger class.

Check your answers.

For further exploration

Introduction to generic types in JDK 5.0

Java theory and practice: Generics gotchas

The Java - Generics

Java - Generics

IBM Code: Java journeys

Previous: Regular expressionsNext: I/O


Downloadable resources


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java development
ArticleID=1036642
ArticleTitle=Unit 20: Generics
publish-date=09142016