Java primitive types
- byte short int long float double
- char int
Compile-time vs Run-time types
Circle c = new ColouredCircle(...);The compile-time type of c is Circle. This does not change.
The run-time type of c is ColouredCircle, which can change, e.g. c = new Circle();
Variance of Types
- Let be a complex type based on type . is:
- Covariant if implies
- Contravariant if implies
- Invariant if is neither covariant nor contravariant: and have no relationship
- Examples
- Covariant: Java arrays are covariant (reference type arrays only, not primitive arrays)
- Invariant: Java generics are invariant
// Covariant
Integer[] ints;
Object[] objects;
objects = ints; // no compiler error, Integer[] <: Object[] (arrays are covariant)
ints = objects; // error
// Invariant
Pair<Integer, Integer> ints = // ...
Pair<Object, Object> objects = // ...
objects = ints; // error
ints = objects; // errorHow Generic Types Work
- Compiler checks types
- Compiler removes generics (Type Erasure), replaces them with either
Objectfor unbound generic types or the first type bound for bound generic types
class Pair<S implements Abc & Def, T> {
private S first;
private T second;
public Pair(S first, T second) {// ... }
public S getFirst() { return this.first; }
public T getSecond() { return this.second; }
}After the compiler has checked the types, and is happy, this becomes:
class Pair {
private Abc first;
private Object second;
public Pair(Abc first, Object second) {// ... }
public Abc getFirst() { return this.first; }
public Object getSecond() { return this.second; }
}also,
Pair<String, Integer> p = new Pair<String, Integer>("hello", 1);
Integer i = p.getSecond();becomes:
Pair p = new Pair("hello", 1);
Integer i = (Integer) p.getSecond();Wildcard Types
- See Wildcard Syntax
- Upper-bound wildcard
A<? extends S>means the generic type parameter can be anything that is a subtype of S- For any type
S,A<S><:A<? extends S>(noteS<:S) - Covariance: If
S<:T, thenA<? extends S><:A<? extends T>- So,
A<S><:A<? extends T>, due to transitive property
- So,
- Lower-bound wildcard
A<? super S>means the generic type parameter can be anything that is a supertype of S- For any type
S,A<S><:A<? super S>(noteS<:S) - Contravariance: If
S<:T, thenA<? super T><:A<? super S>- So,
A<T><:A<? super S>, due to transitive property
- So,
- Unbounded wildcard
A<?>means the generic type parameter can be anythingA<?>is a supertype of allA<...>
- mnemonic PECS: producer
extends; consumersuper- In above example, in the
copyFrommethod,Container<? extends T> c- We call
c.get(), wherecis “producing” a value - So, we use
extends
- We call
- In the
copyTomethod,Container<? super T> c- We call
c.set(), wherecis “consuming” a value - So, we use
super
- We call
- In above example, in the
Type Inference
// example class A
public <T extends GetAreable> T findLargest(Seq<? extends T> seq)
// ...
Shape s = A.findLargest(new Seq<Circle>(0));Sources of Type Constraints
- Type bound:
<T extends GetAreable>→T <: GetAreable - Target typing:
Shape s = ...→T <: Shape - Upper bound wildcard:
Seq<? extends T>→Seq<Circle> <: Seq<? extends T>→Circle <: T
Inference
- Consider all type constraints, in solving for
TT <: GetAreableT <: ShapeCircle <: T (<: Object)
- Solve the constraints, if possible, else, compiler error. note
Shape <: GetAreableCircle <: T <: Shape
- Determine lower bound, that is the type of
T, or use the rules: 2.Type1 <: T <: Type2→Tinferred asType13.Type1 <: T (<: Object)→Tinferred asType 14.T <: Type2→Tinferred asType 2
VarArgs
- When using a generic varArgs, type erasure causes issues
<T> Int method(T... elements): elements is erased toObject[]@SafeVarargsneeds to be applied to the method to say this is ok- Method needs to be private or final
- Safe: If your method only depends on the fact that the elements of the array are instances ofÂ
T - Unsafe: If it depends on the fact that the array is an instance ofÂ
T[]
When to use what
- Exact Type Parameter (
<T>orTypeName): Use When You Need Invariance- Scenario: You need to work with a specific, exact type. You might put items of type
Tinto a structure and expect to get items of the same exact typeTback out. - Example:
List<String> names = new ArrayList<>(); names.add("Alice"); String name = names.get(0);- You addStrings and getStrings back.ArrayList<T>itself uses<T>. - Rule: Use an exact type parameter when the code depends on knowing the precise type for both input and output operations relative to the generic type.
- Scenario: You need to work with a specific, exact type. You might put items of type
- Unbounded Wildcard (
?): Use When the Type Doesn’t Matter- Scenario: Your code works regardless of the specific type parameter, often because it only uses functionality from the
Objectclass, or methods provided by the generic class itself that don’t depend on the type parameter (likeList<?>.size()). - Example:
void printListSize(List<?> list) { System.out.println(list.size()); }- Getting the size doesn’t depend on the list’s element type.Class<?>is another common example. - Rule: If the code works for any type and doesn’t rely on type-specific operations,
?offers maximum flexibility.
- Scenario: Your code works regardless of the specific type parameter, often because it only uses functionality from the
- Upper-Bounded Wildcard (
? extends Type): Use for Covariance (Reading/Producers)- Scenario: You primarily need to get values out of a generic structure (read-only access). The structure might hold
Typeor any subtype ofType. You treat the elements as being at least ofType. - PECS Principle: “Producer Extends”. Use
extendswhen the generic structure acts as a producer (source) of values for your code. - Example:
double sumOfList(List<? extends Number> list) { double sum = 0.0; for (Number n : list) { sum += n.doubleValue(); } return sum; }- You can readNumberobjects (or subtypes likeInteger,Double) from the list. You can safely calldoubleValue()because anything extendingNumberhas it. You cannot safely add arbitraryNumbers to this list because you don’t know the exact subtype it holds (it could beList<Integer>, and adding aDoublewould be wrong). - Rule: Use
? extends Typefor input parameters representing data sources (producers) from which you will readTypeinstances.
- Scenario: You primarily need to get values out of a generic structure (read-only access). The structure might hold
- Lower-Bounded Wildcard (
? super Type): Use for Contravariance (Writing/Consumers)- Scenario: You primarily need to put values into a generic structure (write-only access). The structure must be able to hold
Typeor any supertype ofType. - PECS Principle: “Consumer Super”. Use
superwhen the generic structure acts as a consumer (destination) for values from your code. - Example:
void addIntegers(List<? super Integer> list) { list.add(1); list.add(2); }- You can safely addIntegerobjects to this list because you know it can holdIntegeror one of its supertypes (likeNumberorObject). You cannot safely assume you can read anIntegerfrom it (you might only get anObject). - Rule: Use
? super Typefor input parameters representing data destinations (consumers) into which you will writeTypeinstances.
- Scenario: You primarily need to put values into a generic structure (write-only access). The structure must be able to hold
- Bounded Type Parameter (
<T extends Type>): Use When Defining Constrained Generic Methods/Types- Scenario: You are defining a generic method or class, and you need to refer to the type parameter
Tmultiple times within the method/class body. Crucially, you also need to guarantee thatThas certain capabilities (methods/properties) defined by the boundingType. - Difference from Wildcards: Wildcards (
?) are about making method arguments more flexible. Bounded type parameters (<T extends>) are about constraining the type parameter itself when you declare it for a method or class. - Example:
<T extends Comparable<T>> T findMax(T item1, T item2) { if (item1.compareTo(item2) >= 0) { return item1; } else { return item2; } }- We define a typeTwhich must implementComparable<T>so we can callcompareTo. We useTdirectly for parameters and the return type. - Rule: Use
<T extends Bound>when declaring a generic method or type where the logic requires the typeTto have the methods/properties defined byBound, and you need to refer toTspecifically in the implementation.
- Scenario: You are defining a generic method or class, and you need to refer to the type parameter
Generally
- Defining a Generic Class/Interface?
- Use Type Parameters (
<T>,<K, V>). - Use Bounded Type Parameters (
<T extends SomeType>) if you need to guarantee capabilities forTwithin the class/interface implementation.
- Use Type Parameters (
- Defining a Generic Method?
- Use Type Parameters (
<T>) if:- You need to refer to the exact type
Tmultiple times (e.g., relating parameter types, or parameter and return type). - The method needs both read and write access to a collection based on
T.
- You need to refer to the exact type
- Use Bounded Type Parameters (
<T extends SomeType>) if the criteria above apply AND you need to call methods fromSomeTypeon objects of typeT. - Consider Wildcards (
?,? extends,? super) for parameters if the type parameter is used only once in the signature and PECS applies (see below).
- Use Type Parameters (
- Specifying a Method Parameter Type (Using an existing Generic Type like
List<E>):- Need to read elements? Use
? extends Type(Producer Extends). Allows callers to pass lists ofTypeor its subtypes. - Need to add elements? Use
? super Type(Consumer Super). Allows callers to pass lists ofTypeor its supertypes. - Need to both read (as
Type) and add (instances ofType)? Don’t use a wildcard. Use the exact type (e.g.,List<Type>) or make the method generic (<T> void process(List<T> list)if applicable). - Don’t need specific type guarantees (only
Objectmethods)? Use plain?(e.g.,printList(List<?> list)). - Does the type parameter appear only once in the method signature? Prefer wildcards (
? extends Tor? super T) over declaring a method type parameter (<E extends T> void method(List<E> list)) for simplicity if PECS applies. (Effective Java Item 31).
- Need to read elements? Use
- Specifying a Return Type?
- Generally, avoid wildcards in return types. The caller usually needs a specific type or a named type parameter to work with the result effectively. Use
List<T>,Box<T>,Optional<SpecificType>, etc.
- Generally, avoid wildcards in return types. The caller usually needs a specific type or a named type parameter to work with the result effectively. Use
- Specifying a Local Variable Type?
- You can use wildcards, but often it’s clearer to use a specific type or a type parameter captured from the context if possible. Wildcards here are less common than in parameters.