Java Generics Concepts
Internal
Overview
Generics, or generic types, or parameterized types are a Java language extension and a set of compiler features introduced in Java 5 and improved in subsequent Java releases, which allow writing more reliable, type-safe code. Generics make certain categories of type-related bugs detectable at compile time.
Generics enable types (classes and interfaces) and individual methods to be parameterized with other types when they are declared in the source code. Type parameters are formal parameters of a type or of a method much like a function parameters allow the function code to produce different results when invoked in the presence of different arguments. Type parameters provide a way to re-use the same code with different inputs - other types in this case.
public class SomeClass<T> {
public T doSomething(T t) {
System.out.println(t);
return t;
}
public <V> V doSomethingElse(V v) {
System.out.println(v);
return v;
}
}
public class Main() {
public static void main(String[] args) {
SomeClass<String> scs = new SomeClass<>();
String s = scs.doSomething("something"); // no cast
// scs.doSomething(new Object()); // compile-time failure: "error: incompatible types: Object cannot be converted to String"
SomeClass<Integer> sci = new SomeClass<>();
Integer i = sci.doSomething(1); // no cast
Double d = sci.doSomethingElse(1.0d); // no cast
}
}
Java code that uses generics has several benefits over non-generic code:
- Allows for stronger type checks at compile time.
- Removes the need to cast.
- Enables programmers to implement generic algorithms - algorithms that work on collections of different types, can be customized, are type safe and easier to read.
Generic Type
Generic Type Declaration
A generic type or a parameterized type is a class or an interface that is parameterized over one or more types.
The syntax of a generic type declaration is:
class|interface name<T1, T2, ... Tn> { // ... }
Type Parameters
The type parameter section is delimited by angle brackets (<...>
). The type parameters are also called type variables. A type variable can be any non-primitive type: any class type, any interface type, any array type, or even another type variable. The type variables can be used anywhere inside the class:
private T1 a; private T2 b; public void something(T1 t1, T2 t2) { // ... }
By convention, type parameter names are single, uppercase letters. Without this convention it would be difficult to tell the difference between a type variable and an ordinary class or interface name. The most commonly used parameter names are:
- E - Element
- K - Key
- V - value
- N - Number
- T - type
- S, U, V, etc. - 2nd, 3rd and 4th types
Generic Type Invocation
To use a generic type from code, the generic type variable must be declared by replacing the type variables with concrete type values, such as String
or Integer
. This is called "invoking the type", or "using the type". A generic type invocation can be thought to be similar to an ordinary method invocation, where instead of passing method arguments, we are passing type arguments, which are actual types. As such, a variable that has a generic type is declared as follows:
SomeType<String> s;
It is possible to invoke a parameterized type using another parameterized type, with all its type parameters replaced by concrete type arguments:
SomeType<List<String>> s;
Note that type parameters and type arguments are not the same, and are not interchangeable, in the same way function parameters and arguments are not the same. See:
To instantiate a class of a generic type, use the new
keyword as usual, but replace the generic type parameter with an actual class or interface - the type argument:
SomeType<String> s = new SomeType<String>();
The Diamond <>
Starting with Java 7, the type arguments required to invoke the constructor of a generic class can be replaced with an empty set of type arguments <>
, as long as the compiler can infer the type arguments from the context:
SomeType<Integer> st = new SomeType<>();
Raw Type
A raw type is a generic type invoked without any type argument. The SomeType
class declared in Overview invoked as below creates a raw type:
SomeType rawSt = new SomeType();
Raw types show up in legacy code because many API classes, such as the Collections classes, were not generic prior to Java 5. While allowing raw type invocations, the compiler warns:
For backward compatibility, assigning a parameterized type to its raw type is allowed:
SomeType rawSt = new SomeType();
SomeType<String> st = rawSt; // compiler will issue a warning: unchecked conversion.
Note that a non-generic class or interface, meaning a type that was not declared as generic, is NOT a raw type.
Organizatorium
Type Inference
Covariance
Let T and S be two types (class or function types), such that S is a subtype of T. If method m of T is overridden in S, then the corresponding types from the m's signature can either preserve the relationship between T and S (the type used in S is a subtype of the corresponding type in T), reverse the relationship (the type used in S is a super type of the type used in T), or neither preserve nor reverse this relationship. If they preserve the relationship to T and S, we say they are covariant, if they reverse the relationship of T and S, we say they are contravariant.