
| Code Snippet | Valid? | The Simple Reason |
|---|---|---|
class Gen<T> implements Intf<T> |
YES | Both use the flexible label "T". They match. |
class Norm implements Intf<T> |
NO | Normal class isn't flexible; it doesn't know "T". |
class Norm implements Intf<Integer> |
YES | The class picked a specific type (Integer). |
class class0 implements Intf<Integer> |
YES | Specific types always work for non-generic classes. |
class class5<T> ... Intf<Integer> |
NO | Conflict! Can't be flexible (T) and fixed (Integer). |
// 1. PASSING THE PLACEHOLDER
// Class Gen defines 'T', so it is allowed
// to pass that 'T' to the Interface.
// They are both flexible.
class Gen<T> implements Intf<T> { }
// 2. SEALING THE TYPE
// Class Norm is NOT generic, but it
// provides a concrete type (Integer).
// The Interface is now "locked" to Integer.
class Norm implements Intf<Integer> { }
Rule: To use 'T', the class must own 'T' in its signature.
// ERROR: UNDEFINED SYMBOL
// Norm is a regular class. It has no
// definition for 'T', so it can't
// pass it to the interface.
class Norm implements Intf<T> { }
// ERROR: TYPE CONFLICT
// Gen<T> promises flexibility, but
// 'implements Intf<Integer>' forces
// a specific type. You can't be both!
class Gen<T> implements Intf<Integer> { }
Problem: You cannot reference a generic label you haven't declared.
Introduced in Java 7 to reduce boilerplate code.
// Before Java 7 (Repetitive):
List<String> list = new ArrayList<String>();
// Java 7+ (Clean):
List<String> list = new ArrayList<>();
List<String>.<>.String.
A Raw Type occurs when you use a generic class (like ArrayList) without specifying the type (e.g., <String>). It is a "Legacy Mode" kept for older code.
// Compiler knows it's a String
List<String> safe = new ArrayList<>();
safe.add("Java");
// safe.add(123); // COMPILER ERROR
The compiler "watches your back" to prevent errors.
// Compiler doesn't know the type
List raw = new ArrayList();
raw.add("Java");
raw.add(123); // NO ERROR HERE!
// BOOM! Runtime ClassCastException
String s = (String) raw.get(1);
You are responsible for every error.
If we use a simple <T>, the compiler allows any object. But you can't do math on a String!
// This class is intended for Math...
class Calculator<T> {
T[] nums;
double average() {
// ERROR: The compiler doesn't know if T is a number!
// It won't let you use .doubleValue()
}
}
// T MUST be a subclass of Number
// (Integer, Double, Float, etc.)
class Stats<T extends Number> {
T[] nums;
double average() {
double sum = 0;
for(T n : nums) {
// Valid! Compiler knows
// T is a Number.
sum += n.doubleValue();
}
return sum / nums.length;
}
}
Extends works for both Classes and Interfaces here!
// T must be a Number
// AND it must be Comparable
class MultiBound<T extends Number & Comparable<T>> {
// This allows us to do math
// AND compare two values
// to find the largest.
boolean isGreater(T a, T b) {
return a.doubleValue() > b.doubleValue();
}
}
Note: Use '&' to join multiple requirements.
<T extends UpperBound>
& symbol.
Subtyping
The Rule: Even if String is a Object, List<String> is NOT a List<Object>.
List<String> str = new ArrayList<>();
// List<Object> obj = str; // ERROR
Java prevents this to stop you from adding an Integer into a String list.
// Use '?' to allow subtyping
List<? extends Object> obj = str;
The ? acts as a flexible "type placeholder."
<? extends T>: Read-Only (Get items out).<? super T>: Write-Only (Put items in).
Wildcards (?) provide the flexibility that strict Generics forbid.
Accepts anything. Read-only.
Upper Bound. Perfect for Reading.
Lower Bound. Perfect for Writing.
List<Integer> is NOT a List<Number>.| Wildcard Type | Meaning | Best Used For |
|---|---|---|
<?> (Unbounded) |
"Anything goes." | Counting or printing generic lists. |
<? extends T> (Upper) |
"T or its children." | Reading data (Producers). |
<? super T> (Lower) |
"T or its parents." | Writing/Adding data (Consumers). |
List<Integer> is not a List<Number>.
Java enforces this strictly to prevent type pollution (e.g., trying to add a Double into an Integer list).
List<Integer> ints = new ArrayList<>();
ints.add(10);
// ERROR: Incompatible types!
// Even though Integer IS-A Number,
// List<Integer> IS-NOT-A List<Number>
List<Number> numList = ints;
// WHY? If this worked, I could do:
numList.add(3.14); // Adding a Double to an Integer list!
List<Integer> ints = new ArrayList<>();
ints.add(10);
// VALID: Use '?' to allow flexibility.
// "I accept a list of Numbers OR any child"
List<? extends Number> flexibleList = ints;
// Note: You can READ from this safely
Number n = flexibleList.get(0);
// But you CANNOT add to it (Read-Only)
// flexibleList.add(3.14); // ERROR
| Type | Syntax | Rule |
|---|---|---|
| Upper Bound | ? extends T |
Allows subtyping for Reading. |
| Lower Bound | ? super T |
Allows subtyping for Writing. |
Use <?> when your code doesn't care about the specific type.
// Accepts List<String>, List<Integer>, etc.
void processList(List<?> list) {
// We can READ as Object
for (Object obj : list) {
System.out.println(obj);
}
}
null) because Java doesn't know what it's supposed to hold.Object).
Allows a method to accept a list of the type OR any of its sub-types.
// The "Relaxed" Method:
// Accepts List, List, etc.
public void printSum(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n.doubleValue());
}
}
// How we call it:
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2);
printSum(ints); // VALID!
printSum(doubles); // VALID!
Use <? super T> when you need to WRITE (add) items into a collection.
// The "Bucket" Method:
// We can add Integer, but we can't guarantee what we get back.
public void addItems(List<? super Integer> list) {
list.add(5); // VALID
list.add(10); // VALID
// WARNING: When you read, you only know it's an Object.
// Object obj = list.get(0);
}
<? extends T>) - Use for READING.
<? super T>) - Use for WRITING.
List extends Number> numbers = new ArrayList< Integer>();
Number num = numbers.get(0); // Valid: Reading is allowed
// numbers.add(10); // Invalid: Cannot add to a list of unknown subtype
List super Integer> numbers = new ArrayList< Number>();
numbers.add(10); // Valid: Adding is allowed
// Integer num = numbers.get(0); // Invalid: Reading returns Object
Object obj = numbers.get(0); // Valid: Reading as Object
List < Integer> numbers = new ArrayList<>();
numbers.add(10); // Valid: Adding is allowed
Integer num = numbers.get(0); // Valid: Reading is allowed
Generics are scaffolding: The compiler uses them to build the house safely, then tears them down before the program runs.
The compiler sees your "Labels":
List<String> list = new ArrayList<>();
list.add("Hello");
// Compiler: "I see the String label.
// I will block any Integers!"
The labels are ERASED:
List list = new ArrayList();
list.add("Hello");
// JVM: "I don't see any labels.
// To me, this is just a List of Objects."
List<int> is illegal because int isn't an Object.new T) if the recipe is erased!print(List<String>) and print(List<Integer>) look identical to the computer.