Menu
Coddy logo textTech

Wildcards (?, extends, super)

Part of the Object Oriented Programming section of Coddy's Java journey — lesson 57 of 87.

Bounded type parameters restrict generics to specific types, but what about when you're working with existing generic types and don't know—or don't care about—the exact type parameter? This is where wildcards come in.

The unbounded wildcard ? represents an unknown type. It's useful when you want to accept any parameterized type:

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

printList(new ArrayList<String>());   // Works
printList(new ArrayList<Integer>());  // Works

The upper bounded wildcard ? extends Type accepts the specified type or any of its subclasses. Use it when you need to read from a generic structure:

public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}

sumNumbers(Arrays.asList(1, 2, 3));        // List of Integer
sumNumbers(Arrays.asList(1.5, 2.5));       // List of Double

The lower bounded wildcard ? super Type accepts the specified type or any of its superclasses. Use it when you need to write to a generic structure:

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

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // Works - Number is a supertype of Integer

A helpful guideline: use extends when you only read (producer), use super when you only write (consumer). This is known as the PECS principle—Producer Extends, Consumer Super.

challenge icon

Challenge

Easy

Let's build a zoo feeding system that demonstrates all three types of wildcards! You'll create a hierarchy of animals and food, then write utility methods that use unbounded, upper bounded, and lower bounded wildcards to handle different feeding scenarios.

You'll organize your code across four files:

  • Food.java: Create a simple class hierarchy for food. Start with a base class Food that has a private field name (String), a constructor to initialize it, and a getName() method. Then create two subclasses in the same file: Meat extends Food (constructor calls super(name)) and Vegetable extends Food (same pattern).
  • Animal.java: Create a base class Animal with a private field species (String), a constructor, and a getSpecies() method. Create two subclasses: Carnivore and Herbivore, each extending Animal and calling the parent constructor.
  • ZooFeeder.java: This is where wildcards shine! Create a utility class with three static methods:

    printInventory(List<?> items) - Uses an unbounded wildcard to print any list. Loop through and print each item using toString(), one per line.

    calculateTotalFood(List<? extends Food> foods) - Uses an upper bounded wildcard to read from a list of any Food type. Return the count of items as an int. This demonstrates reading from a producer.

    addMeatToStock(List<? super Meat> stock, String meatName) - Uses a lower bounded wildcard to write to a list that can hold Meat. Create a new Meat object with the given name and add it to the stock. This demonstrates writing to a consumer.

  • Main.java: Bring your zoo feeding system together! You'll receive two inputs: a meat name (String) and a vegetable name (String).

    First, create an ArrayList<Food> and add a Meat object with the meat name and a Vegetable object with the vegetable name. Print Food inventory: then call printInventory with this list.

    Next, print a blank line, then print Total food items: [count] using calculateTotalFood on your food list.

    Finally, create a new ArrayList<Food> called meatStock. Print a blank line, then Adding to meat stock.... Call addMeatToStock with this list and the string "Beef". Then print Meat stock after adding: and call printInventory on the meat stock.

You will receive two inputs in order: a meat name and a vegetable name.

Override toString() in your Food class to return the food's name, so printing works correctly. Notice how each wildcard type serves a different purpose: ? for maximum flexibility when you just need to read as Object, ? extends when reading specific types, and ? super when writing to a collection!

Cheat sheet

Java provides three types of wildcards for working with generic types when the exact type parameter is unknown or irrelevant.

Unbounded Wildcard (?)

Represents an unknown type and accepts any parameterized type:

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

printList(new ArrayList<String>());   // Works
printList(new ArrayList<Integer>());  // Works

Upper Bounded Wildcard (? extends Type)

Accepts the specified type or any of its subclasses. Use when you need to read from a generic structure:

public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}

sumNumbers(Arrays.asList(1, 2, 3));        // List of Integer
sumNumbers(Arrays.asList(1.5, 2.5));       // List of Double

Lower Bounded Wildcard (? super Type)

Accepts the specified type or any of its superclasses. Use when you need to write to a generic structure:

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

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // Works - Number is a supertype of Integer

PECS Principle

Producer Extends, Consumer Super: Use extends when you only read (producer), use super when you only write (consumer).

Try it yourself

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

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String meatName = scanner.nextLine();
        String vegetableName = scanner.nextLine();
        
        // TODO: Create an ArrayList<Food> and add Meat and Vegetable objects
        
        // TODO: Print "Food inventory:" then call printInventory
        
        // TODO: Print blank line, then "Total food items: [count]" using calculateTotalFood
        
        // TODO: Create new ArrayList<Food> called meatStock
        
        // TODO: Print blank line, then "Adding to meat stock..."
        
        // TODO: Call addMeatToStock with meatStock and "Beef"
        
        // TODO: Print "Meat stock after adding:" then call printInventory on meatStock
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming