Popular Searches
Popular Course Categories
Popular Courses

Exploring Lower-Bounded Wildcards and Unbounded Wildcards

Exploring Lower-Bounded Wildcards and Unbounded Wildcards

Generics and Type Safety

Exploring Lower-Bounded Wildcards and Unbounded Wildcards in Java

Course: Core Java Training

Topic: Exploring Lower-Bounded Wildcards and Unbounded Wildcards

Course Link: Core Java Training Course

Demo Link: Register for a Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Wildcards in Java Generics

Java Generics allow classes, interfaces, and methods to work with different data types while providing compile-time type safety. A wildcard is represented by the question mark (?) and represents an unknown type. Wildcards are especially useful when a method needs to accept collections whose element types are related but not exactly the same.

Java supports three common wildcard forms:

  • Unbounded wildcard: List<?> — a list whose element type is unknown.
  • Upper-bounded wildcard: List<? extends Number> — a list of an unknown type that is Number or a subtype of Number.
  • Lower-bounded wildcard: List<? super Integer> — a list of an unknown type that is Integer or a supertype of Integer.

This topic focuses on unbounded and lower-bounded wildcards, which help developers design flexible methods for handling collections of different types.

2. Learning Objectives

  • Understand what wildcards represent in Java Generics.
  • Explain the syntax and purpose of unbounded wildcards.
  • Use lower-bounded wildcards with the super keyword.
  • Understand which operations are safe with each wildcard type.
  • Differentiate List<Object> from List<?>.
  • Apply wildcards to collection-processing methods.
  • Understand the PECS guideline for choosing wildcard bounds.

3. What Is an Unbounded Wildcard?

An unbounded wildcard uses the question mark without an explicit upper or lower bound. For example:

List<?> items;

This declaration means that items refers to a list of some unknown element type. The list could contain strings, integers, doubles, or objects of another reference type.

For example, all the following lists can be passed to a method that accepts List<?>:

List<String> names;
List<Integer> numbers;
List<Double> prices;

The wildcard does not mean that the list can freely accept every possible type. It means that the element type is unknown to the code using that reference.

4. Why Use Unbounded Wildcards?

Suppose a method only needs to print the contents of a list. It does not need to know whether the list contains strings, integers, or another type. Using List<?> allows the method to accept lists of many different element types.

Example: Printing a list

import java.util.Arrays;
import java.util.List;

public class UnboundedWildcardExample {
    public static void printList(List<?> items) {
        for (Object item : items) {
            System.out.println(item);
        }
    }

    public static void main(String[] args) {
        List<String> names = Arrays.asList("Amit", "Neha");
        List<Integer> numbers = Arrays.asList(10, 20, 30);

        printList(names);
        printList(numbers);
    }
}

Output:

Amit
Neha
10
20
30

Explanation:

  • printList() accepts a list with any element type.
  • Each element can safely be read as an Object, because every Java reference type is derived from Object.
  • The method does not need to declare a named type parameter such as <T> because it does not need to link the element type to another parameter or return value.

5. Understanding List<?> and List<Object>

These declarations look similar, but they have different meanings and are not interchangeable.

Feature List<Object> List<?>
Meaning A list whose element type is specifically Object. A list whose element type is unknown.
Accepts List<String> directly? No. Yes.
Accepts List<Integer> directly? No. Yes.
Can add a string? Yes, because a string is an Object. No, because the actual element type is unknown.
Can read an element as Object? Yes. Yes.
Typical purpose Store arbitrary objects in a list specifically typed as Object. Read or inspect collections of unknown element type.

Example showing the difference

import java.util.ArrayList;
import java.util.List;

public class ListTypeDifference {
    public static void main(String[] args) {
        List<Object> objectList = new ArrayList<>();
        objectList.add("Java");
        objectList.add(100);

        List<String> stringList = new ArrayList<>();
        stringList.add("Generics");

        List<?> unknownList = stringList;

        System.out.println(objectList);
        System.out.println(unknownList);

        // This is not allowed:
        // objectList = stringList;

        // This is also not allowed:
        // unknownList.add("New value");
    }
}

Output:

[Java, 100]
[Generics]

Key point: Java generics are invariant. Even though String is a subtype of Object, List<String> is not a subtype of List<Object>. A wildcard such as List<?> provides a way to accept lists of unknown element types safely.

6. What Can You Do with an Unbounded Wildcard?

Since the element type is unknown, Java restricts operations that could insert an incompatible object.

Allowed operations

  • Read an element and store it in an Object variable.
  • Check the list's size using size().
  • Check whether it is empty using isEmpty().
  • Iterate through its elements.
  • Clear the list using clear(), if the underlying list supports modification.
  • Remove elements through supported collection operations.
  • Add null, because null can be assigned to any reference type, although the underlying collection may impose its own restrictions.

Operations that are not allowed

List<?> items = new ArrayList<String>();

// Compile-time errors:
// items.add("Java");
// items.add(100);

Object value = items.get(0);

The compiler cannot determine whether items is a list of strings, integers, or another type. Therefore, it cannot guarantee that adding a string or integer would be safe. Reading as Object is safe because every element is an object reference.

7. What Is a Lower-Bounded Wildcard?

A lower-bounded wildcard uses the super keyword. Its general syntax is:

<? super Type>

For example:

List<? super Integer>

This means that the list's actual element type is Integer or a supertype of Integer, such as Number or Object.

Consider these valid declarations:

List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();

List<? super Integer> first = integers;
List<? super Integer> second = numbers;
List<? super Integer> third = objects;

Each assignment is valid because the list can hold an Integer value according to its declared element type.

8. Why Use Lower-Bounded Wildcards?

Lower-bounded wildcards are especially useful when a method needs to insert values into a collection. If a method must add integers, requiring only List<Integer> would be unnecessarily restrictive. A parameter of type List<? super Integer> also accepts lists of Number and Object.

Example: Adding integers to a list

import java.util.ArrayList;
import java.util.List;

public class LowerBoundExample {
    public static void addNumbers(List<? super Integer> list) {
        list.add(10);
        list.add(20);
        list.add(30);
    }

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        List<Number> numbers = new ArrayList<>();
        List<Object> objects = new ArrayList<>();

        addNumbers(integers);
        addNumbers(numbers);
        addNumbers(objects);

        System.out.println(integers);
        System.out.println(numbers);
        System.out.println(objects);
    }
}

Output:

[10, 20, 30]
[10, 20, 30]
[10, 20, 30]

Explanation:

  • The method accepts lists whose element type is Integer or a supertype of Integer.
  • Adding an Integer is safe for all accepted list types.
  • The method cannot safely add an arbitrary Number or an arbitrary Object, because the actual list might be a List<Integer>.

9. Reading Values from a Lower-Bounded Wildcard

A lower-bounded wildcard is useful for adding values, but it provides less information when reading elements. For a variable declared as List<? super Integer>, the compiler does not know whether the list is a List<Integer>, a List<Number>, or a List<Object>.

List<? super Integer> values = new ArrayList<Number>();
values.add(42);

Object item = values.get(0);

// This does not compile:
// Integer number = values.get(0);

System.out.println(item);

Output:

42

The result of get() can safely be assigned to an Object. It cannot generally be assigned directly to an Integer, because the list's declared element type may be Number or Object.

10. Unbounded vs. Lower-Bounded Wildcards

Feature Unbounded Wildcard Lower-Bounded Wildcard
Syntax <?> <? super Integer>
Meaning Unknown element type. Unknown element type that is Integer or a supertype.
Primary purpose Read or inspect values without needing their exact type. Insert values of the specified type safely.
Can add an integer? No, except for null. Yes, when the lower bound is Integer.
Can read as Object? Yes. Yes.
Typical example Printing arbitrary lists. Adding integers to lists of integers or their supertypes.

11. Choosing the Correct Wildcard: PECS

PECS stands for Producer Extends, Consumer Super. It is a common guideline for deciding which wildcard to use in collection APIs.

  • Producer extends: Use ? extends T when a collection provides values to your code and you mainly need to read them as type T.
  • Consumer super: Use ? super T when your code supplies values of type T to a collection.
  • Unbounded wildcard: Use ? when the exact type is irrelevant and you only need operations that do not depend on the element type.

For example, a method that copies elements from a source list into a destination list can use an upper-bounded source and a lower-bounded destination.

import java.util.List;

public class CopyExample {
    public static <T> void copy(
            List<? extends T> source,
            List<? super T> destination) {

        for (T item : source) {
            destination.add(item);
        }
    }

    public static void main(String[] args) {
        List<Integer> source = List.of(1, 2, 3);
        List<Number> destination =
                new java.util.ArrayList<>();

        copy(source, destination);

        System.out.println(destination);
    }
}

Output:

[1, 2, 3]

In this example, the source produces values that can be treated as T, while the destination consumes values of type T. This is a practical application of PECS.

12. Common Mistakes and How to Fix Them

Mistake Why It Is Incorrect Correct Approach
Adding a string to List<?> The actual element type is unknown. Use a suitably typed list or a lower-bounded wildcard when insertion is required.
Assigning List<String> to List<Object> Generic types are invariant. Use List<?> for an unknown list type or choose an appropriate bound.
Adding any object to List<? super Integer> The actual list may be a List<Integer>. Add only Integer values or values compatible with the lower bound.
Reading from a lower-bounded list directly into an Integer The actual element type could be Number or Object. Read into an Object variable unless a safe, justified conversion is available.
Using a wildcard when a named type is needed A wildcard does not provide a reusable type name. Use a generic type parameter such as <T> when the same type must link multiple arguments or a return value.

13. Practical Applications

Application 1: Printing collection values

Use List<?> when a utility method only needs to display or inspect elements without changing their values.

Application 2: Adding values to collections

Use a lower-bounded wildcard such as List<? super Integer> when a method needs to insert integers into lists of integers or their supertypes.

Application 3: Reusable collection utilities

Methods that copy, transfer, or populate data can combine upper- and lower-bounded wildcards to support a broad range of compatible collection types.

Application 4: Working with APIs

Wildcards are widely used in Java collection APIs because they allow methods to accept compatible generic types without sacrificing compile-time type safety.

14. Practice Exercises

  1. Create a method that accepts List<?> and prints each element.
  2. Write a method that accepts List<? super String> and adds three strings to the list.
  3. Create an integer list, a number list, and an object list. Pass each to a method that accepts List<? super Integer>.
  4. Attempt to add an integer to List<?> and explain the compiler error.
  5. Write a generic copy method using List<? extends T> as the source and List<? super T> as the destination.
  6. Explain why List<String> cannot be assigned to List<Object>, but can be assigned to List<?>.

15. Mini Project: Generic List Utility

Objective: Create a utility class that can inspect lists of any element type and insert specified values into compatible destination lists.

Requirements:

  • Implement a method called displayItems(List<?> items).
  • Implement a method called addDefaultNames(List<? super String> destination).
  • Test the methods with lists of strings, integers, numbers, and objects where appropriate.
  • Print the resulting contents and observe which assignments compile.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GenericListUtility {
    public static void displayItems(List<?> items) {
        for (Object item : items) {
            System.out.println(item);
        }
    }

    public static void addDefaultNames(
            List<? super String> destination) {
        destination.add("Java");
        destination.add("Generics");
        destination.add("Wildcards");
    }

    public static void main(String[] args) {
        List<Integer> numbers =
                Arrays.asList(10, 20, 30);
        displayItems(numbers);

        List<Object> values = new ArrayList<>();
        addDefaultNames(values);
        displayItems(values);
    }
}

Expected output:

10
20
30
Java
Generics
Wildcards

This project demonstrates how unbounded wildcards support general inspection and lower-bounded wildcards support safe insertion into compatible collections.

16. Interview Questions and Answers

Q1. What is an unbounded wildcard in Java?

An unbounded wildcard is represented by ?, as in List<?>. It represents a collection whose element type is unknown.

Q2. What is a lower-bounded wildcard?

A lower-bounded wildcard uses super, as in List<? super Integer>. It accepts a list whose element type is Integer or a supertype of Integer.

Q3. Why can values not generally be added to List<?>?

The actual element type is unknown. Adding an arbitrary value could violate the list's element type, so the compiler prevents it. The exception is null, subject to the underlying collection's restrictions.

Q4. Why can an integer be added to List<? super Integer>?

Every permitted element type is Integer or a supertype of it, so an integer is compatible with all accepted list types.

Q5. What is the difference between List<Object> and List<?>?

List<Object> is specifically a list of objects, while List<?> can refer to a list with any element type. A List<String> can be passed to a method accepting List<?>, but not to one accepting List<Object>.

Q6. What does PECS mean?

PECS means Producer Extends, Consumer Super. Use extends when values are primarily produced for reading, and super when values are consumed by a destination.

Q7. Can a lower-bounded wildcard be used with an interface?

Yes. For example, List<? super Runnable> can refer to a list whose element type is Runnable or a supertype of that interface, such as Object.

Q8. Can a wildcard have both an upper and lower bound simultaneously?

No. A wildcard can specify an upper bound with extends or a lower bound with super, but not both in the same wildcard expression.

17. Quick Revision Checklist

  • ? represents an unknown type.
  • List<?> can accept lists with different element types.
  • Values read from List<?> can safely be treated as Object.
  • List<? super Integer> accepts lists of Integer and its supertypes.
  • Lower-bounded wildcards are useful when inserting values into a collection.
  • List<Object> and List<?> are different types.
  • PECS helps select the correct wildcard bound.
  • Wildcards provide flexibility while preserving compile-time type safety.

18. Conclusion

Unbounded and lower-bounded wildcards are important tools for writing flexible Java code with Generics. An unbounded wildcard, such as List<?>, is useful when the exact element type is irrelevant and the code needs to inspect or process a collection. A lower-bounded wildcard, such as List<? super Integer>, is useful when the code needs to add values of a particular type to a collection that can safely hold them.

Understanding these wildcard forms, their restrictions, and the PECS guideline helps developers build reusable collection utilities and work confidently with Java's type system.

Continue learning: Explore the Core Java Training course, register for a course demo, or visit the JustAcademy website.

Previous Next
whatsapp