How to Use Bounded Wildcards in Java Methods

Introduction

When working with Java generics, one of the key concepts to understand is the use of wildcards. Wildcards in Java offer flexibility when dealing with parameterized types. However, when you want more control over the types, you can use bounded wildcards. This feature allows you to specify an upper or lower bound for the type parameter, which makes your code more adaptable and type-safe.

In this article, we’ll explore how to use bounded wildcards in Java methods, explaining both upper-bounded and lower-bounded wildcards, and when each is appropriate to use. Along with theoretical explanations, we will also dive into practical code examples that illustrate how bounded wildcards work in Java.

What are Wildcards in Java?

Before diving into bounded wildcards, let’s first review what wildcards are in Java.

A wildcard is a special type parameter that allows you to write more generic code that works with different types. In Java, the wildcard is represented by a ?. For example:

List<?> list;

This means the list can hold objects of any type, but you cannot directly add elements to this list, except for null.

Types of Bounded Wildcards

There are two types of bounded wildcards in Java:

  1. Upper-bounded wildcards (? extends T):
    • The wildcard is restricted to a type that is either T or a subtype of T.
    • Use ? extends T when you want to read from a structure, but you do not intend to write into it.
  2. Lower-bounded wildcards (? super T):
    • The wildcard is restricted to a type that is either T or a supertype of T.
    • Use ? super T when you want to write into a structure but do not care about the type you’re reading.

Understanding Upper-Bounded Wildcards (? extends T)

An upper-bounded wildcard means that the generic type can be of type T or any class that extends T. It’s useful when you want to restrict the types that can be passed into a method but still retain the ability to handle a broader range of types. The upper-bounded wildcard is defined as ? extends T.

Example of Upper-Bounded Wildcards

Here’s an example of an upper-bounded wildcard being used in a Java method:

public class WildcardExample {

public static void printNumbers(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num);
}
}

public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.1, 2.2, 3.3);

printNumbers(intList); // Works with Integer
printNumbers(doubleList); // Works with Double
}
}

Explanation:

  • List<? extends Number> means the list can hold any subclass of Number, including Integer, Double, Float, etc.
  • The method printNumbers can accept lists of different numeric types without specifying an exact type, offering flexibility.

When to Use Upper-Bounded Wildcards

  • When you only need to read data: Since you can’t add to a List<? extends T>, this wildcard is ideal for methods that don’t modify the collection.
  • When you want to accept multiple subtypes: For example, if you need a method that works with both Integer and Double, the upper-bounded wildcard allows you to accept both.

Understanding Lower-Bounded Wildcards (? super T)

A lower-bounded wildcard means that the generic type can be of type T or any class that is a superclass of T. It’s useful when you want to write to a collection but don’t care about the specific type you’re working with. The lower-bounded wildcard is defined as ? super T.

Example of Lower-Bounded Wildcards

public class WildcardExample {

public static void addIntegers(List<? super Integer> list) {
list.add(10);
list.add(20);
}

public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addIntegers(numberList); // Works because Number is a supertype of Integer
System.out.println(numberList);
}
}

Explanation:

  • List<? super Integer> means the list can hold Integer objects or any type that is a superclass of Integer, like Number or Object.
  • The method addIntegers can safely add Integer objects to the list because we know the list will accept Integer and possibly superclasses of Integer.

When to Use Lower-Bounded Wildcards

  • When you need to write data: The lower-bounded wildcard allows you to add objects of type T or its subclasses.
  • When you know the list can accept values of type T: Since ? super T allows writing to the list, it’s often used when you want to add elements of type T or a subclass.

Combining Bounded Wildcards with Generics

You can combine both upper and lower bounds with generics for more complex scenarios. The syntax can be extended to accept parameters that are bounded by both upper and lower bounds.

Example of Upper and Lower Bounded Wildcards Together

public class WildcardExample {

public static <T> void printInRange(List<? extends T> list, T lowerBound, T upperBound) {
for (T element : list) {
if (((Comparable<T>) element).compareTo(lowerBound) >= 0 &&
((Comparable<T>) element).compareTo(upperBound) <= 0) {
System.out.println(element);
}
}
}

public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5);
printInRange(intList, 2, 4); // Prints 2, 3, 4
}
}

Explanation:

  • The method printInRange uses an upper-bounded wildcard (? extends T) to accept a list of elements that extend T (in this case, Integer).
  • We are also using the Comparable interface to compare the elements within the range, demonstrating how both upper and lower bounds can be used together in a method.

Limitations of Bounded Wildcards

While bounded wildcards provide a lot of flexibility, there are some limitations you should be aware of:

  1. Reading from collections: With ? extends T, you can only read elements but cannot add elements (except for null), as the compiler cannot guarantee the exact type of the elements in the collection.
  2. Writing to collections: With ? super T, you can only safely add objects of type T or its subtypes, but reading from the collection can be problematic since the exact type is unknown.

Common Use Cases

  1. Working with Collections: Bounded wildcards are useful when you want to create methods that can operate on collections of different types, such as lists of numbers, or lists of any subclass of a particular class.
  2. Polymorphism: Wildcards allow you to write methods that are polymorphic in terms of the types they accept, making your code cleaner and more reusable.

Conclusion

In Java, bounded wildcards provide a powerful way to work with generics while still enforcing type safety. By using upper-bounded wildcards (? extends T) and lower-bounded wildcards (? super T), you can create methods that are flexible and adaptable to a wide variety of types. Understanding when and how to use these wildcards will help you write more efficient and reusable code.

Key Points Recap:

  • Upper-bounded wildcards (? extends T) are useful when reading data from a collection.
  • Lower-bounded wildcards (? super T) are useful when writing data to a collection.
  • Bounded wildcards increase flexibility and help avoid unnecessary type casting.

By mastering bounded wildcards, you will improve your Java generics knowledge and be able to create methods that handle a variety of types with type safety in mind.

Please follow and like us:

Leave a Comment