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]");
|