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>()); // WorksThe 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 DoubleThe 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 IntegerA 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
EasyLet'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 classFoodthat has a private fieldname(String), a constructor to initialize it, and agetName()method. Then create two subclasses in the same file:Meatextends Food (constructor callssuper(name)) andVegetableextends Food (same pattern).Animal.java: Create a base classAnimalwith a private fieldspecies(String), a constructor, and agetSpecies()method. Create two subclasses:CarnivoreandHerbivore, 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 usingtoString(), 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 anint. 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 newMeatobject 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 aMeatobject with the meat name and aVegetableobject with the vegetable name. PrintFood inventory:then callprintInventorywith this list.Next, print a blank line, then print
Total food items: [count]usingcalculateTotalFoodon your food list.Finally, create a new
ArrayList<Food>calledmeatStock. Print a blank line, thenAdding to meat stock.... CalladdMeatToStockwith this list and the string"Beef". Then printMeat stock after adding:and callprintInventoryon 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>()); // WorksUpper 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 DoubleLower 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 IntegerPECS 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
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsFields (Attributes)Constructor MethodConstructor OverloadingRecap - Simple Calculator4Inheritance
Basic Inheritance (extends)The super KeywordMethod Overriding (@Override)Constructor ChainingThe Object ClassSingle & Multilevel InheritWhy No Multi Class InheritRecap - Employee Hierarchy7Special Methods & Object Class
toString() Methodequals() and hashCode()clone() MethodcompareTo() and ComparableComparator InterfaceRecap - Custom Sorting2Access Modifiers & Encapsulate
Access Levels OverviewGetter and Setter MethodsInformation HidingThe final KeywordRecap - Bank Account Manager5Polymorphism
Method Overloading BasicsMethod Overriding (Run-Time)Upcasting and DowncastingThe instanceof OperatorAbstract Classes and MethodsRecap - Shape Calculator8Advanced OOP Concepts
Composition vs InheritanceAggregation vs CompositionInner Nested & Anonymous ClassEnums and Enum MethodsRecords (Java 16+)Sealed Classes (Java 17+)3Class Props & Static Member
Instance vs Static VariablesStatic MethodsStatic BlocksConstants (static final)Recap - Counter & Utility6Interfaces & Abstract Classes
Introduction to InterfacesImplementing InterfacesMulti Interface ImplemenDefault & Static in InterfaceAbstract Classes vs InterfacesFunctional InterfacesRecap - Payment System9Generics
Introduction to GenericsGeneric ClassesGeneric MethodsBounded Type ParametersWildcards (?, extends, super)Recap - Generic Container