Understanding Wildcards and Upper Bounds in Java
Course: Core Java Training
Topic: Understanding Wildcards and Upper Bounds
Course Link: Core Java Training Course
Demo Link: Register for a Course Demo
Academy Website: JustAcademy
1. Introduction to Wildcards in Java
Wildcards are a feature of Java Generics that allow a variable, method parameter, or reference to work with an unknown type. A wildcard is represented by the question mark (?). Wildcards are especially useful when writing reusable methods that should accept collections of different related types.
For example, a method that processes numbers may need to accept a List<Integer>, a List<Double>, or a List<Number>. Wildcards help express this flexibility without sacrificing compile-time type safety.
Learning Objectives
- Understand Java wildcards and their purpose.
- Learn the difference between generic type parameters and wildcards.
- Understand unbounded and bounded wildcards.
- Use upper-bounded wildcards with the
extends keyword.
- Learn why upper-bounded collections are suitable for reading data.
- Understand generic invariance and common type compatibility errors.
- Apply wildcards in practical Java programs.
2. What Is a Wildcard?
A wildcard represents an unknown type in a generic type declaration. It is commonly used when a method does not need to know the exact element type of a collection.
For example:
List<?> items;
This declaration means that items can refer to a list of some unknown reference type. The type could be String, Integer, Double, or another reference type.
Example: Unbounded Wildcard
import java.util.Arrays;
import java.util.List;
public class Main {
public static void printItems(List<?> items) {
for (Object item : items) {
System.out.println(item);
}
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Asha", "Rahul");
List<Integer> numbers = Arrays.asList(10, 20, 30);
printItems(names);
printItems(numbers);
}
}
Output:
Asha
Rahul
10
20
30
The method accepts lists with different element types because it does not depend on a specific element type. Each retrieved element can safely be treated as an Object.
3. Types of Wildcards in Java
Java supports three common wildcard forms for generic types.
| Wildcard Type |
Syntax |
Purpose |
| Unbounded wildcard |
<?> |
Accepts a generic type with an unknown element type. |
| Upper-bounded wildcard |
<? extends Number> |
Accepts Number or a subtype of Number. |
| Lower-bounded wildcard |
<? super Integer> |
Accepts Integer or a supertype of Integer. |
This lesson focuses on wildcards and upper bounds. Lower bounds are included for comparison because choosing between extends and super is an important part of generic programming.
4. Understanding Upper-Bounded Wildcards
An upper-bounded wildcard restricts the unknown type to a specified type or one of its subtypes. It uses the extends keyword.
Syntax:
GenericType<? extends UpperBound>
For example:
List<? extends Number> numbers;
This reference can point to a list of Number, Integer, Double, Float, or another subclass of Number. It does not mean that every element must be an instance of Number's exact class; it means the actual list element type is an unknown subtype of Number, possibly Number itself.
Why Use Upper Bounds?
- They allow one method to accept collections of related types.
- They provide access to methods declared by the upper-bound type.
- They help avoid unnecessary duplication of methods for each subtype.
- They maintain compile-time type checking.
5. Example of an Upper-Bounded Wildcard
Consider a method that calculates the sum of a list of numbers. It should accept lists containing Integer, Double, or other Number subclasses.
import java.util.Arrays;
import java.util.List;
public class Main {
public static double calculateSum(
List<? extends Number> numbers) {
double sum = 0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
public static void main(String[] args) {
List<Integer> integers =
Arrays.asList(10, 20, 30);
List<Double> decimals =
Arrays.asList(1.5, 2.5, 3.0);
System.out.println(calculateSum(integers));
System.out.println(calculateSum(decimals));
}
}
Output:
60.0
7.0
Explanation
List<? extends Number> accepts lists whose element type is Number or a subclass of Number.
- The method can retrieve each element as Number.
- The
doubleValue() method converts each numeric value to a double.
- The same method works with integer and decimal lists.
Without the wildcard, a parameter declared as List<Number> would not accept a List<Integer> or a List<Double>.
6. Understanding the extends Keyword in Wildcards
In a wildcard declaration, extends represents an upper bound. The type may be the bound itself or a subtype of that bound. When the bound is an interface, the supplied type must implement that interface.
Consider this class hierarchy:
class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Dog is barking");
}
}
class Cat extends Animal {
public void meow() {
System.out.println("Cat is meowing");
}
}
A method declared with List<? extends Animal> can accept lists of Animal, Dog, or Cat.
import java.util.Arrays;
import java.util.List;
public class Main {
public static void displayAnimals(
List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
}
public static void main(String[] args) {
List<Dog> dogs =
Arrays.asList(new Dog(), new Dog());
List<Cat> cats =
Arrays.asList(new Cat());
displayAnimals(dogs);
displayAnimals(cats);
}
}
Output:
Animal is eating
Animal is eating
Animal is eating
The method can call eat() because that method is available through the Animal upper bound. It cannot directly call bark() or meow() on an Animal reference because those methods are not declared by Animal.
7. Reading and Writing with Upper-Bounded Wildcards
Upper-bounded wildcards are useful when a method needs to read values from a collection. However, they restrict adding non-null values because the actual element type is unknown.
List<Integer> integers = new ArrayList<>();
List<? extends Number> numbers = integers;
Number value = numbers.get(0); // Valid if the list has an element.
// numbers.add(10); // Compile-time error
// numbers.add(3.5); // Compile-time error
numbers.add(null); // Allowed
Both Integer and Double extend Number, but the compiler does not know which specific subtype the list stores. If the list actually stores Integers, adding a Double would violate its type safety. Therefore, adding arbitrary non-null values is not allowed through this reference.
An upper-bounded wildcard is often described as suitable for reading. It is not strictly read-only, because operations such as clear() and removing elements are still possible, and null can be added.
8. Why List<Integer> Is Not List<Number>
A common misconception is that because Integer extends Number, List<Integer> should also extend List<Number>. Java generic types are invariant, so this relationship does not exist.
Invalid example:
List<Integer> integers = new ArrayList<>();
// Compile-time error:
List<Number> numbers = integers;
If this assignment were allowed, code could insert a Double into a list intended to hold only Integers.
Correct example using an upper-bounded wildcard:
List<Integer> integers = new ArrayList<>();
List<? extends Number> numbers = integers;
The wildcard allows the reference to represent a list of an unknown Number subtype without incorrectly claiming that the list stores every kind of Number.
9. Upper-Bounded Wildcards with Interfaces
Upper bounds can also refer to interfaces. This allows a method to accept objects that implement a required capability, regardless of their concrete class.
import java.util.List;
interface Printable {
void print();
}
class Invoice implements Printable {
public void print() {
System.out.println("Printing invoice");
}
}
class Report implements Printable {
public void print() {
System.out.println("Printing report");
}
}
public class Main {
public static void printDocuments(
List<? extends Printable> documents) {
for (Printable document : documents) {
document.print();
}
}
}
The method accepts lists of Invoice or Report because both classes implement Printable. The extends keyword is used with the interface bound as well as with class bounds.
10. Upper-Bounded Wildcards vs. Generic Type Parameters
Wildcards and named type parameters can both make methods flexible, but they are useful in different situations.
| Feature |
Upper-Bounded Wildcard |
Named Type Parameter |
| Syntax |
List<? extends Number> |
<T extends Number> |
| Purpose |
Accepts a compatible generic type without naming its exact type. |
Names a type that can be reused across parameters and return values. |
| Common use |
Reading elements from related collections. |
Relating multiple values or the return type to the same type. |
| Type relationship |
The actual type is unknown to the method. |
The type variable T represents a consistent type throughout the declaration. |
Use a wildcard when the method only needs to accept a compatible collection. Use a named type parameter when the method needs to express a relationship between two or more types.
11. Comparing Upper-Bounded and Lower-Bounded Wildcards
Upper- and lower-bounded wildcards solve different problems.
| Feature |
Upper Bound |
Lower Bound |
| Syntax |
<? extends Number> |
<? super Integer> |
| Meaning |
Number or a subtype of Number. |
Integer or a supertype of Integer. |
| Typical purpose |
Read values as the upper-bound type. |
Insert values of the lower-bound type. |
| Safe non-null insertion |
Generally not possible through the wildcard reference. |
Values of the lower-bound type can be inserted. |
| Example |
Summing numbers from a list. |
Adding integers to a destination list. |
Lower-Bounded Example for Comparison
import java.util.List;
public class Main {
public static void addIntegers(
List<? super Integer> destination) {
destination.add(10);
destination.add(20);
destination.add(30);
}
}
This method can accept a list of Integer, Number, or Object. Each of these types can safely store an Integer value.
12. Practical Example: Finding the Average of Numbers
Upper-bounded wildcards are useful for utility methods that process collections of numeric subtypes.
import java.util.Arrays;
import java.util.List;
public class NumberUtility {
public static double average(
List<? extends Number> values) {
if (values.isEmpty()) {
throw new IllegalArgumentException(
"The list must not be empty");
}
double total = 0;
for (Number value : values) {
total += value.doubleValue();
}
return total / values.size();
}
public static void main(String[] args) {
List<Integer> marks =
Arrays.asList(80, 90, 100);
List<Double> prices =
Arrays.asList(10.5, 20.5, 30.0);
System.out.println(average(marks));
System.out.println(average(prices));
}
}
Output:
90.0
20.333333333333332
The method accepts lists with different Number subtypes. It explicitly checks for an empty list to avoid division by zero and provides a meaningful error for that case.
13. Common Mistakes When Using Wildcards
| Mistake |
Why It Is Incorrect |
Correct Approach |
Assigning List<Integer> to List<Number> |
Generic types are invariant. |
Use List<? extends Number> when reading. |
Adding a Double to List<? extends Number> |
The actual element type may be Integer or another subtype. |
Use a suitable lower-bounded wildcard for a destination list. |
| Assuming the wildcard type is exactly Number |
The wildcard represents an unknown subtype, possibly Number itself. |
Treat retrieved values as Number, not as a specific subtype. |
| Calling subclass-only methods |
The compiler only guarantees methods available through the upper bound. |
Use methods declared by the bound or perform an appropriate type check. |
| Using wildcards unnecessarily |
Excessive wildcard use can make APIs harder to understand. |
Use a named type parameter when a relationship between types is required. |
14. Best Practices for Wildcards and Upper Bounds
- Use
? extends T when a method primarily consumes or reads values from a source of type T or its subtypes.
- Use the upper-bound type when declaring variables for retrieved values.
- Use
? super T when a method needs to insert values of type T into a destination.
- Use
? when the exact type does not matter and only general Object-level operations are needed.
- Avoid attempting to add arbitrary non-null values to an upper-bounded collection.
- Prefer a named type parameter if the same type must be related across multiple parameters or the return type.
- Keep method signatures simple and use bounds only when they communicate a real requirement.
- Test methods with several compatible collection types, including subclasses and interface implementations.
15. Practice Exercises
Exercise 1: Print Any List
Create a method that accepts List<?> and prints all its elements. Test it with String and Integer lists.
Exercise 2: Sum Numeric Values
Create a method accepting List<? extends Number> that returns the sum of its elements. Test it with Integer and Double lists.
Exercise 3: Display Animals
Create Animal, Dog, and Cat classes. Write a method accepting List<? extends Animal> and call a common method on each element.
Exercise 4: Add Integer Values
Write a method using List<? super Integer> that inserts several integers. Test it with lists of Integer, Number, and Object.
Exercise 5: Fix the Type Error
Explain why the following assignment fails and correct it.
List<Integer> values = new ArrayList<>();
List<Number> numbers = values;
16. Mini Project: Generic Number Analyzer
Objective: Build a utility that calculates the sum, average, and maximum value from lists of different numeric types.
import java.util.Arrays;
import java.util.List;
public class NumberAnalyzer {
public static double sum(
List<? extends Number> values) {
double total = 0;
for (Number value : values) {
total += value.doubleValue();
}
return total;
}
public static double average(
List<? extends Number> values) {
if (values.isEmpty()) {
throw new IllegalArgumentException(
"List cannot be empty");
}
return sum(values) / values.size();
}
public static void main(String[] args) {
List<Integer> scores =
Arrays.asList(70, 80, 90);
List<Double> measurements =
Arrays.asList(1.5, 2.5, 3.5);
System.out.println("Score sum: " + sum(scores));
System.out.println("Score average: " + average(scores));
System.out.println("Measurement sum: " + sum(measurements));
System.out.println("Measurement average: " + average(measurements));
}
}
Output:
Score sum: 240.0
Score average: 80.0
Measurement sum: 7.5
Measurement average: 2.5
Project extensions:
- Add a method to find the minimum value.
- Handle empty lists consistently across all methods.
- Test with Integer, Double, Float, and Long values.
- Document why the utility uses an upper-bounded wildcard.
17. Interview Questions and Answers
Q1. What is a wildcard in Java Generics?
A wildcard, represented by ?, represents an unknown type in a generic declaration.
Q2. What is an upper-bounded wildcard?
An upper-bounded wildcard uses ? extends Type to accept the specified type or one of its subtypes.
Q3. Why are upper-bounded wildcards useful?
They allow methods to accept collections of different related types while maintaining type safety.
Q4. Can values be added to a list declared as List<? extends Number>?
Arbitrary non-null values cannot be added because the actual element type is unknown. Null can be added, and other operations such as removal may still be possible.
Q5. What is the difference between List<Number> and List<? extends Number>?
List<Number> refers specifically to a list whose element type is Number. List<? extends Number> can refer to a list of Number or any subtype of Number.
Q6. Can upper bounds use interfaces?
Yes. For example, List<? extends Runnable> can represent a list whose element type implements Runnable.
Q7. Why is List<Integer> not a subtype of List<Number>?
Java generic types are invariant. Allowing this assignment could permit values of an incompatible type to be inserted into the original list.
Q8. When should an unbounded wildcard be used?
Use ? when the exact type is irrelevant and the method can work with values as Objects or use operations that do not depend on the type parameter.
Q9. What is the difference between extends and super wildcards?
? extends T represents T or a subtype and is often used for reading. ? super T represents T or a supertype and is often used for inserting values of type T.
Q10. What does PECS mean?
PECS stands for “Producer Extends, Consumer Super.” Use extends when a structure produces values for the method to read and super when it consumes values supplied by the method.
18. Quick Revision Checklist
- Understand that
? represents an unknown generic type.
- Know the difference between unbounded, upper-bounded, and lower-bounded wildcards.
- Remember the syntax
<? extends Type>.
- Use upper-bounded wildcards to accept a type and its subtypes.
- Understand why arbitrary non-null insertion is restricted through upper-bounded references.
- Remember that Java generic types are invariant.
- Use
? super T when a destination must accept values of type T.
- Apply PECS when deciding which wildcard to use.
19. Conclusion
Wildcards make Java generic APIs more flexible by allowing methods to work with unknown types. Upper-bounded wildcards are particularly useful when processing collections of a base type and its subclasses. Understanding their syntax, reading restrictions, and relationship to generic invariance helps developers write reusable, type-safe Java applications.
Continue learning with the Core Java Training Course, register through the course demo registration page, or visit the JustAcademy website.
Further Reading: Oracle Java Tutorial: Upper-Bounded Wildcards and Dev.java: Wildcards.