Java
Generics
Type Safety
Programming
Inheritance

When do Java generics require ? extends T instead of T and is there any downside of switching?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Understanding Java Generics and Bounded Wildcards

Java generics provide a way to create classes, interfaces, and methods with a placeholder for the types they operate with. The primary goal is to ensure type safety while still allowing code reusability. There are situations where you'd use <? extends T> instead of <T>, which mainly involves how you want to handle type flexibility and constraints.

The Concept of Bounded Wildcards

In Java generics, a wildcard (?) is a symbol that stands for an unknown type. The bound for a wildcard can either be upper or lower:

  • <? extends T>: This refers to an unknown type that is a subtype (or the same type) as T.
  • <? super T>: This refers to an unknown type that is a supertype (or the same type) as T.

The commonly used upper-bounded wildcard <? extends T> is useful when you want to read from a generic object but not modify it, ensuring you work with at least type T or any of its subtypes.

When to Use <? extends T>

Reading from Collections

Imagine a method that outputs items from a list. If the list can contain items of type T or any subtype of T, the signature should use <? extends T>:

java
1public static void printListOfAnimals(List<? extends Animal> list) {
2    for (Animal animal : list) {
3        System.out.println(animal);
4    }
5}

In this example, printListOfAnimals can take a list of Dog, Cat, or any subtype instances of Animal.

Producer-Extends Principle

This principle, often cited as "PECS" (Producer Extends Consumer Super), states that:

  • Use extends when you want to get values out of a structure and don’t want to insert.
  • Use super when you want to insert values.

Downsides of Switching from <T> to <? extends T>

If you switch your method signature from <T> to <? extends T>, it comes with certain limitations:

  • Insertion Restriction: You cannot add elements to a structure defined with <? extends T> because Java cannot guarantee the type safety of insertions. For example:
java
1  public static void addAnimalToList(List<? extends Animal> list) {
2      // Error: Cannot add an Animal to a List<? extends Animal>
3      // list.add(new Dog());
4  }

In contrast, using <T>, you can insert elements of type T:

java
  public static <T> void addAnimalToList(List<T> list, T element) {
      list.add(element); // Safe addition
  }
  • Generic Flexibility: While <? extends T> provides greater flexibility in terms of the types it can hold, <T> allows for more explicit control, especially when you need to both read and write to the collection.

Examples with Code

Case Study: Covariant Lists

Suppose you need to manage a list of numbers and calculate a sum:

java
1public static double sumOfNumbers(List<? extends Number> list) {
2    double sum = 0;
3    for (Number number : list) {
4        sum += number.doubleValue();
5    }
6    return sum;
7}

This code will work with any list of elements that are a type of Number, such as Integer, Double, etc., but it cannot modify the list.

Conclusion

Choosing between <T> and <? extends T> heavily depends on the context of operations you plan to perform and the level of type flexibility required. <? extends T> is suitable for producing (reading), while <T> remains robust for scenarios that involve producing and consuming (reading and writing).

Summary Table

Usage<T><? extends T>
Type FlexibilityLess flexible, strict to type TMore flexible, accepts subtypes of T
InsertionSupports adding elementsDoes not support adding elements
Read/WriteRead and Write operations allowableRead-only operations
Use CaseBoth producing and consumingProducing/Reading only

Course illustration
Course illustration

All Rights Reserved.