Flylib.com

Books Software

 
 
 

Chapter 4. Declarations


Chapter 4. Declarations

This chapter discusses how to declare a generic class. It describes constructors, static members , and nested classes, and it fills in some details of how erasure works.



4.1. Constructors

In a generic class, type parameters appear in the header that declares the class, but not in the constructor:

class

Pair<T, U>

{
  private final T first;
  private final U second;
  public

Pair

(T first, U second) { this.first=first; this.second=second; }
  public T getFirst() { return first; }
  public U getSecond() { return second; }
}

The type parameters T and U are declared at the beginning of the class, not in the constructor. However, actual type parameters are passed to the constructor whenever it is invoked:

Pair<String, Integer> pair = new

Pair<String, Integer>

("one",2);
assert pair.getFirst().equals("one") && pair.getSecond() == 2;

Look Out for This! A common mistake is to forget the type parameters when invoking the constructor:

Pair<String, Integer> pair = new

Pair

("one",2);

This mistake produces a warning, but not an error. It is taken to be legal, because Pair is treated as a raw type, but conversion from a raw type to the corresponding parameterized type generates an unchecked warning; see Section 5.3, which explains how the -Xlint:unchecked flag can help you spot errors of this kind.



4.2. Static Members

Because generics are compiled by erasure, at run time the classes List<Integer> , List<String> , and List<List<String>> are all implemented by a single class, namely List . You can see this using reflection:

List<Integer> ints = Arrays.asList(1,2,3);
List<String> strings = Arrays.asList("one","two");
assert ints.getClass() == strings.getClass();

Here the class associated with a list of integers at run time is the same as the class associated with a list of strings.

One consequence is that static members of a generic class are shared across all instantiations of that class, including instantiations at different types. Static members of a class cannot refer to the type parameter of a generic class, and when accessing a static member the class name should not be parameterized.

For example, here is a class, Cell <T> , in which each cell has an integer identifier and a value of type T :

class Cell<T> {
  private final int id;
  private final T value;
  private

static

int count = 0;
  private

static

synchronized int nextId() { return count++; }
  public Cell(T value) { this.value=value; id=nextId(); }
  public T getValue() { return value; }
  public int getId() { return id; }
  public

static

int getCount() { return count; }
}

A static field, count , is used to allocate a distinct identifier to each cell. The static nextId method is synchronized to ensure that unique identifiers are generated even in the presence of multiple threads. The static getCount method returns the current count.

Here is code that allocates a cell containing a string and a cell containing an integer, which are allocated the identifiers and 1 , respectively:

Cell<String> a = new Cell<String>("one");
Cell<Integer> b = new Cell<Integer>(2);
assert a.getId() == 0 && b.getId() == 1 && Cell.getCount() == 2;

Static members are shared across all instantiations of a class, so the same count is incremented when allocating either a string or an integer cell.

Because static members are independent of any type parameters, we are not permitted to follow the class name with type parameters when accessing a static member:


Cell

.getCount();

// ok


Cell<Integer>

.getCount();

// compile-time error


Cell<?>

.getCount();

// compile-time error


The count is static, so it is a property of the class as a whole, not any particular instance.

For the same reason, you may not refer to a type parameter anywhere within a static member. Here is a second version of Cell , which attempts to use a static variable to keep a list of all values stored in any cell:

class Cell2<T> {
  private final T value;
  private

static

List<

T

> values = new ArrayList<

T

>();

// illegal

public Cell(T value) { this.value=value; values.add(value); }
  public T getValue() { return value; }
  public

static

List<

T

> getValues() { return values; }

// illegal

}

Since the class may be used with different type parameters at different places, it makes no sense to refer to T in the declaration of the static field values or the static method getValues() , and these lines are reported as errors at compile time. If we want a list of all values kept in cells , then we need to use a list of objects, as in the following variant:

class Cell2<T> {
  private final T value;
  private

static

List<

Object

> values = new ArrayList<

Object

>();

// ok

public Cell(T value) { this.value=value; values.add(value); }
  public T getValue() { return value; }
  public

static

List<

Object

> getValues() { return values; }

// ok

}

This code compiles and runs with no difficulty:

Cell2<String> a = new Cell2<String>("one");
Cell2<Integer> b = new Cell2<Integer>(2);
assert Cell2.getValues().toString().equals("[one, 2]");