Java Generics: Type Parameters, Wildcards, Erasure - Quiz
Total: 8 questions
1. What are generics in Java and why are they needed?
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?
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?
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?
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 asObject, nothing can be added (exceptnull).List<? extends Number>-Numberor a subclass; elements are read asNumber, nothing can be added.List<? super Integer>-Integeror a superclass;Integervalues can be added, elements are read only asObject.
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?
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?
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?
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?
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>- useMatrix<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
Tin 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>)andprint(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.