Type Parameters and Wildcards
Generics in Java provide powerful mechanisms to create flexible, reusable code by allowing you to define classes, interfaces, and methods with type parameters.
These type parameters can then be specified when the generic element is instantiated or called.
Two key aspects of Java generics are type parameters and wildcards, which allow for more precise and flexible type constraints.
Type Parameters
Type parameters are used to define generic classes, interfaces, and methods.
They act as placeholders for the actual types that will be specified later.
Defining Type Parameters
A type parameter is defined using angle brackets (<>)
and can be given any valid identifier (typically a single uppercase letter like T, E, K, V, etc.).
Syntax
class ClassName<T> {
private T data;
public void setData(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
Example
Consider a simple Box class that uses a type parameter:
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello, Generics!");
System.out.println("Stored String: " + stringBox.getValue()); // Outputs: Stored String: Hello, Generics!
Box<Integer> intBox = new Box<>();
intBox.setValue(42);
System.out.println("Stored Integer: " + intBox.getValue()); // Outputs: Stored Integer: 42
}
}
Explanation
Generic Class Definition: class
Box<T>
defines a generic class Box with a type parameter T.Method Usage: Methods setValue and getValue use the type parameter T.
Wildcards
Wildcards in Java generics are used to specify unknown types.
They are represented by the question mark (?) and can be used in various forms to create more flexible methods and classes.
Types of Wildcards
Unbounded Wildcards (?)
Bounded Wildcards (Upper and Lower Bounds)
Unbounded Wildcards
An unbounded wildcard represents an unknown type.
It's useful when you want to work with a generic type but don't care about the specific type.
Syntax
public void printBox(Box<?> box) {
System.out.println(box.getValue());
}
Example
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void printBox(Box<?> box) {
System.out.println("Box contains: " + box.getValue());
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello, Wildcards!");
printBox(stringBox); // Outputs: Box contains: Hello, Wildcards!
Box<Integer> intBox = new Box<>();
intBox.setValue(123);
printBox(intBox); // Outputs: Box contains: 123
}
}
Explanation
- Unbounded Wildcard Usage:
Box<?>
allowsprintBox to
accept any type ofBox
.
Bounded Wildcards
Upper Bounded Wildcards
An upper bounded wildcard restricts the unknown type to be a specific type or a subtype of that type.
Syntax
public void printUpperBoundedBox(Box<? extends Number> box) {
System.out.println(box.getValue());
}
Example
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void printUpperBoundedBox(Box<? extends Number> box) {
System.out.println("Box contains: " + box.getValue());
}
public static void main(String[] args) {
Box<Integer> intBox = new Box<>();
intBox.setValue(42);
printUpperBoundedBox(intBox); // Outputs: Box contains: 42
Box<Double> doubleBox = new Box<>();
doubleBox.setValue(3.14);
printUpperBoundedBox(doubleBox); // Outputs: Box contains: 3.14
}
}
Explanation
- Upper Bounded Wildcard:
Box<? extends Number>
allowsprintUpperBoundedBox
to acceptBox
instances containingNumber
or its subclasses (Integer
,Double
, etc.).
Lower Bounded Wildcards
- A lower bounded wildcard restricts the unknown type to be a specific type or a supertype of that type.
Syntax
public void printLowerBoundedBox(Box<? super Integer> box) {
System.out.println(box.getValue());
}
Example
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void addNumber(Box<? super Integer> box) {
box.setValue(42);
}
public static void main(String[] args) {
Box<Number> numberBox = new Box<>();
addNumber(numberBox);
System.out.println("Box contains: " + numberBox.getValue()); // Outputs: Box contains: 42
Box<Object> objectBox = new Box<>();
addNumber(objectBox);
System.out.println("Box contains: " + objectBox.getValue()); // Outputs: Box contains: 42
}
}
Explanation
- Lower Bounded Wildcard:
Box<? super Integer>
allows addNumber to acceptBox
instances containingInteger
or any of its supertypes (Number
,Object
, etc.).
Summary
Type Parameters: Used to define generic classes, interfaces, and methods, providing type safety and flexibility.
Syntax:
class ClassName<T> { ... }
Example:
class Box<T> { ... }
Wildcards: Represent unknown types and can be used to create flexible methods and classes.
Unbounded Wildcards (
?
): Accept any type.Syntax:
Box<?>
Example:
public void printBox(Box<?> box) { ... }
Upper Bounded Wildcards (
<? extends Type>
): Accept a specific type or its subtypes.Syntax:
Box<? extends Number>
Example:
public void printUpperBoundedBox(Box<? extends Number> box) { ... }
Lower Bounded Wildcards (
<? super Type>
): Accept a specific type or its supertypes.Syntax:
Box<? super Integer>
Example:
public void addNumber(Box<? super Integer> box) { ... }
Understanding type parameters and wildcards is essential for mastering Java generics and writing flexible, reusable code.