Java Generics: Type Parameters, Wildcards, Erasure - Quiz

Total: 8 questions

1. 

What are generics in Java and why are they needed?

Generics are a type parameterization mechanism introduced in Java 5: a class, interface or method is declared with a type parameter (T), and a concrete type is supplied when it is used - List<String>, Matrix<Integer>.

The main goal is type safety. Without generics a collection stores Object, elements are cast by hand, and a mistake shows up only at runtime:

List rawList = new ArrayList();      // raw type
rawList.add("42");
Integer n = (Integer) rawList.get(0); // compiles, but ClassCastException

List<Integer> numbers = new ArrayList<>();
// numbers.add("42");                // compilation error
numbers.add(42);
Integer m = numbers.get(0);           // no cast needed

With generics the compiler rejects values of the wrong type and inserts the casts itself. A type used without arguments (List instead of List<Integer>) is called a raw type; it exists only for compatibility with old code.

2. 

What does T mean in Java, and what are the naming conventions for type parameters?

T is the name of a type parameter, a placeholder that is replaced by a concrete reference type. In class Matrix<T> the letter T is the type parameter, and in Matrix<Integer> the type Integer is the type argument.

By convention a type parameter is a single uppercase letter: T - any type, E - collection element (List<E>), K/V - key and value (Map<K, V>), N - number, R - result (Function<T, R>), S, U - second and third types.

A class can have several type parameters, separated by commas:

class TwoGen<T, V> { ... }
TwoGen<Integer, String> twoGen = new TwoGen<>(88, "Generics");

Only reference types can be type arguments: Matrix<int> is a compilation error, use Matrix<Integer>. Since Java 7 the right-hand side can use the diamond operator <>, and the compiler infers the arguments from the left-hand side.

3. 

What is a bounded type parameter in Java generics?

A bounded type parameter is a type parameter with an upper bound: <T extends Number> allows only Number and its subclasses. In return, the methods of the bound can be called inside the class:

class Average<T extends Number> {
    double sum(T[] array) {
        double s = 0;
        for (T v : array) s += v.doubleValue(); // a Number method
        return s;
    }
}
// Average<String> - error: type argument String is not within bounds

An interface bound is also written with extends, not implements: <T extends Serializable>. Several bounds are joined with &, and the class must come first: <T extends MyClass & MyInterface1 & MyInterface2> - the argument must be a subclass of MyClass that implements both interfaces.

4. 

What is the ? wildcard in Java, and how does ? extends differ from ? super?

The wildcard ? stands for an unknown type argument. It is needed because parameterized types are invariant: List<Integer> is not a subtype of List<Number>, even though Integer is a subclass of Number.

  • List<?> - a list with any type argument; elements are read as Object, nothing can be added (except null).
  • List<? extends Number> - Number or a subclass; elements are read as Number, nothing can be added.
  • List<? super Integer> - Integer or a superclass; Integer values can be added, elements are read only as Object.

The PECS rule (Producer Extends, Consumer Super): a parameter that supplies elements is declared with ? extends T, and one that accepts elements with ? super T:

static <T> void copy(List<? super T> dest, List<? extends T> src)
5. 

How do you declare a generic method in Java, and how does the compiler determine its type argument?

A generic method declares its own type parameters in angle brackets before the return type. It can be declared in an ordinary, non-generic class, including as static:

public static <T> boolean isIn(T x, T[] array) { ... }

isIn(2, intArray);                           // T inferred as Integer
GenMethodExample.<Integer>isIn(2, intArray); // explicit type argument

Usually the compiler infers the type argument from the call arguments. The catch: isIn("2", intArray) compiles and returns false - T is inferred as a common supertype of String and Integer. With an explicit <Integer> it becomes a compilation error.

Constructors can be generic too, even in a non-generic class: public <T extends Number> GenConstructor(T arg).

6. 

How do you extend a generic class or implement a generic interface?

A subclass (or a class implementing an interface) must pass every type argument up the hierarchy - either as its own parameter or as a concrete type:

class GenericSub<T> extends GenericSuper<T> { ... }          // passes its own T
class IntegerHolder extends GenericSuper<Integer> { ... }     // fixes the type
class LabeledHolder<T, V> extends GenericSuper<T> { ... }    // adds its own parameter

class Echo<T> implements MyInterface<T> { ... }
class Upper implements MyInterface<String> { ... }

class Echo implements MyInterface<T> without declaring T on the class itself does not compile: cannot find symbol: class T. A generic class may also extend an ordinary non-generic class. JDK interfaces work the same way: Comparable<T>, Comparator<T>, List<E>.

7. 

What is type erasure in Java?

Type erasure is how Java implements generics: the compiler checks the type arguments and then removes them from the bytecode. T is replaced by its bound (Object, or for example Number for <T extends Number>), and casts are inserted where the values are used.

That is why List<String> and List<Integer> are the same class at runtime:

System.out.println(new ArrayList<String>().getClass()
        == new ArrayList<Integer>().getClass()); // true

Because of erasure you cannot write new T() - the type is passed explicitly as a Class<T> object (a type token): type.getDeclaredConstructor().newInstance().

Type arguments are erased from objects, but not from declarations: for class IntegerHolder extends GenericSuper<Integer> the method getGenericSuperclass() returns GenericSuper<java.lang.Integer>. The super type token technique (TypeReference in Jackson) is built on this.

8. 

What are the restrictions on generics in Java?

Almost all restrictions follow from type erasure. You cannot:

  • use a primitive as a type argument: Matrix<int> - use Matrix<Integer>;
  • create an object or an array from a type parameter: new T(), new T[10] (generic array creation);
  • create an array of a parameterized type: new GenericSub<Integer>[10] - new GenericSub<?>[10] is allowed;
  • use the class T in a static field or method: static T ob; (a static method with its own parameter, static <V> void m(V v), is fine);
  • overload methods that differ only by type argument: print(List<String>) and print(List<Integer>) - name clash;
  • declare a generic exception class: class MyEx<T> extends Exception.

instanceof GenericSub<?> always works. Since Java 16 a concrete type argument is allowed too, if the cast is checked: for GenericSuper<Integer> x the check x instanceof GenericSub<Integer> compiles, while for Object o the check o instanceof GenericSub<Integer> is a compilation error.

Page 1 of 1