Skip to main content

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<?> allows printBox to accept any type of Box.

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> allows printUpperBoundedBox to accept Box instances containing Number 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 accept Box instances containing Integer 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.