Wildcards in generics
- Eclipse: Oxygen
- Java: 1.8
Introduction
Java generics use wildcards to represent unknown or generic types that can be used in different ways. Wildcards are denoted by the question mark symbol (?) and can be used in upper-bounded, lower-bounded, or unbounded forms.
Types of Wildcards:
There are three types of wildcards used in Java generics:
- Upper bounded wildcard: extends a certain type or interface.
- Lower bounded wildcard: super restricts a certain type or interface.
- Unbounded wildcard: represents any type and can be used to read from a collection of unknown types.
Upper Bounded Wildcard
The upper bounded wildcard extends a certain type or interface, allowing for more flexibility in the types of objects that can be used with a collection. It is denoted by <? extends Type>, where “Type” can be a class or interface. This allows for the use of a collection with objects of any class that is a subclass of “Type”.
Lower Bounded Wildcard
The lower bounded wildcard super restricts a certain type or interface, allowing only objects of that type or its superclasses to be used with a collection. It is denoted by <? super Type>, where “Type” can be a class or interface. This allows for the use of a collection with objects of “Type” or any superclass of “Type”.
Unbounded Wildcard
The unbounded wildcard represents any type and is denoted by <?>. It can be used to read from a collection of unknown types, making it useful when the type of objects in a collection is unknown. It allows for more flexibility in the types of objects that can be used with a collection.
Usage of Wildcards
Wildcards are useful when dealing with collections of different types. They allow for more flexibility in the types of objects that can be used with a collection, making it easier to work with generic types. They can be used with classes, interfaces, and methods to allow for more generic programming and increased code reusability.
The unknown Wildcard
In Java, we use the question mark (?) as a wildcard character in generics to represent an unknown type. This means that if we don’t know what type of List we have, we can only read from the collection, and we can only treat the objects we read as Object instances.
In the code example, there are two classes, one is a superclass and the other is a subclass. We can add different types of objects to a List and then print them using the display method. The List can hold multiple types of objects since we do not know the object’s type, so we type List<?> to indicate that the list is typed to an unknown type.
It could be a List of Vehicles, a List of Cars, a List of Strings, or any other type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
// This is a class that represents a Vehicle with a unique ID. class Vehicle { private int VehicleId; // Constructor that sets the vehicle ID. public Vehicle(int vehicleId) { super(); VehicleId = vehicleId; } // Getter method to get the vehicle ID. public int getVehicleId() { return VehicleId; } // Overrides the toString() method to provide a string representation of the vehicle ID. @Override public String toString() { return "Vehicle [VehicleId=" + VehicleId + "]"; } } // This is a class that represents a Car, which is a subclass of Vehicle. class Car extends Vehicle { private String CarModel; // Constructor that sets the vehicle ID and car model. public Car(int vehicleId, String carModel) { super(vehicleId); CarModel = carModel; } // Getter method to get the car model. public String getCarModel() { return CarModel; } // Overrides the toString() method to provide a string representation of the car model. @Override public String toString() { return "Car [CarModel=" + CarModel + "]"; } } public class App { public static void main(String[] args) { // Create a list of objects that contains Vehicle and Car objects. List<Object> list = new ArrayList<>(); list.add(new Vehicle(10)); list.add(new Vehicle(11)); list.add(new Vehicle(12)); list.add(new Vehicle(13)); list.add(new Car(14, "A14")); // Call the display() method to print out the objects in the list. display(list); } // This is a static method that takes a list of objects and displays them to the console. public static void display(List<?> list) { for (Object element : list) { System.out.println(element); } } } |
Code Explanation:
This code defines two classes – Vehicle and Car.
The vehicle class has a unique ID, which can be set using a constructor and can be accessed using a getter method. Whereas, Car is a subclass of Vehicle and has an additional field, CarModel.
Both classes override the toString() method to provide a string representation of their properties.
In the main() method, a list of objects is created, which contains Vehicle and Car objects. The display() method is then called, which takes a list of objects and prints each object using its toString() method.
The program demonstrates inheritance, polymorphism, and the use of collections in Java. The display() method is a static method, which means it can be called without creating an object of the App class.
Output
1 2 3 4 5 6 7 8 9 |
Vehicle [VehicleId=10] Vehicle [VehicleId=11] Vehicle [VehicleId=12] Vehicle [VehicleId=13] Car [CarModel=A14] |
Upper Bounded Wildcards
An upper-bounded wildcard is a way to restrict the unknown type in a generic class or method to be a specific type or a subtype of that type. It is denoted by the wildcard character (<?>), followed by the “extend” keyword and the upper-bound class or interface name. It allows us to use generic code to work with a wide range of object types, while still enforcing a level of type safety.
For example, if we want to restrict the unknown type to be a subclass of Vehicle, we can declare the wildcard like this:
1 |
public static void display(List<? extends Vehicle> list) |
In this way, the list would only accept objects of the Vehicle class or its child classes. This is useful when we want to apply restrictions on the type of objects that can be passed to a type parameter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
// Define the Vehicle class class Vehicle { // Define the VehicleId field private int VehicleId; // Define the constructor for the Vehicle class public Vehicle(int vehicleId) { super(); VehicleId = vehicleId; } // Define the getter method for VehicleId field public int getVehicleId() { return VehicleId; } // Override the toString() method for the Vehicle class @Override public String toString() { return "Vehicle [VehicleId=" + VehicleId + "]"; } } // Define the Car class, which extends the Vehicle class class Car extends Vehicle { // Define the CarModel field private String CarModel; // Define the constructor for the Car class public Car(int vehicleId, String carModel) { super(vehicleId); CarModel = carModel; } // Define the getter method for the CarModel field public String getCarModel() { return CarModel; } // Override the toString() method for the Car class @Override public String toString() { return "Car [CarModel=" + CarModel + "]"; } } } // Define the main application class public class App { // Define the main method public static void main(String[] args) { // Create a list of Vehicle objects List < Vehicle > list = new ArrayList < > (); list.add(new Vehicle(10)); list.add(new Vehicle(11)); list.add(new Vehicle(12)); list.add(new Vehicle(13)); // Add a Car object to the list of vehicles list.add(new Car(14, "A14")); } // Define the main application class public class App { // Define the main method public static void main(String[] args) { // Create a list of Vehicle objects List < Vehicle > list = new ArrayList < > (); list.add(new Vehicle(10)); list.add(new Vehicle(11)); list.add(new Vehicle(12)); list.add(new Vehicle(13)); // Add a Car object to the list of vehicles list.add(new Car(14, "A14")); // Call the display() method with the list of vehicles display(list); } // Define the display() method that accepts a list of Vehicle objects public static void display(List < ? extends Vehicle > list) { // Loop through the list of vehicles and print each one to the console for (Vehicle element: list) { System.out.println(element); } } } |
Code Explanation:
We have two classes: Vehicle and Car.
Car is a subclass of Vehicle, and it adds an extra field CarModel.
The main method of the App class creates an ArrayList of Vehicle objects, including one Car object, and calls the display method with this list as its argument.
The display method takes a List of objects of a class that extends Vehicle. It then loops through the list and prints out each object using its toString method.
In this case, since all objects in the list are either of type Vehicle or a subclass of Vehicle (in this case, Car), the display method can be called with a List<? extends Vehicle> parameter.
Output
1 2 3 4 5 6 7 8 9 |
Vehicle [VehicleId=10] Vehicle [VehicleId=11] Vehicle [VehicleId=12] Vehicle [VehicleId=13] Car [CarModel=A14] |
Lower Bounded Wildcards
A lower bounded wildcard in Java generics restricts an unknown type to a specific type or its supertype. To declare it, use the wildcard character (<?>), followed by the “super” keyword. An example is
1 |
List<? super Car> |
which implies that the list is typed to either the Car class or a superclass of Car. This feature is useful when dealing with collections of different types, providing more flexibility in the type of objects that can be used.
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class Vehicle { private int VehicleId; // Define the constructor for the Vehicle class public Vehicle(int vehicleId) { super(); VehicleId = vehicleId; } // Define the getter method for VehicleId field public int getVehicleId() { return VehicleId; } // Override the toString() method for the Vehicle class @Override public String toString() { return "Vehicle [VehicleId=" + VehicleId + "]"; } // Define the Car class, which extends the Vehicle class class Car extends Vehicle { // Define the CarModel field private String CarModel; // Define the constructor for the Car class public Car(int vehicleId, String carModel) { super(vehicleId); CarModel = carModel; } // Define the getter method for the CarModel field public String getCarModel() { return CarModel; } // Override the toString() method for the Car class @Override public String toString() { return "Car [CarModel=" + CarModel + "]"; } // Define the main application class public class App { // Define the main method public static void main(String[] args) { // Create a list of Vehicle objects List < Vehicle > list = new ArrayList < > (); list.add(new Vehicle(10)); list.add(new Vehicle(11)); list.add(new Vehicle(12)); list.add(new Vehicle(13)); list.add(new Car(14, "A14")); // Call display method with the list of Vehicle objects display(list); } // Define the display method which accepts a List of Car or its superclasses public static void display(List < ? super Car > list) { // Loop through the list and print each element for (Object element: list) { System.out.println(element); } } } |
Output
1 2 3 4 5 6 7 8 9 |
Vehicle [VehicleId=10] Vehicle [VehicleId=11] Vehicle [VehicleId=12] Vehicle [VehicleId=13] Car [CarModel=A14] |
Let’s see one more example.
In this program, polymorphism application has been applied and dynamically we are able to pick up the correct version of info inside the “for loop”.
We are able to pick up the correct version when the object is of car type and the object is of vehicle type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class Vehicle{ private int VehicleId; public Vehicle(int vehicleId) { super(); VehicleId = vehicleId; } public int getVehicleId() { return VehicleId; } @Override public String toString() { return "Vehicle [VehicleId=" + VehicleId + "]"; } void info(){ System.out.println("vehicle ID is "+getVehicleId()); } } class Car extends Vehicle{ private String CarModel; public Car(int vehicleId, String carModel) { super(vehicleId); CarModel = carModel; } public String getCarModel() { return CarModel; } @Override public String toString() { return "Car [CarModel=" + CarModel + "]"; } @Override void info(){ System.out.println("Car Model is "+getCarModel()); } } public class App { public static void main(String[] args) { List<Vehicle> list = new ArrayList<>(); list.add(new Vehicle(10)); list.add(new Vehicle(11)); list.add(new Vehicle(12)); list.add(new Vehicle(13)); list.add(new Car(14, "A14")); display(list); } public static void display(List<? extends Vehicle> list){ for(Vehicle element: list){ element.info(); } } } |
Output
1 2 3 4 5 6 7 8 9 |
vehicle ID is 10 vehicle ID is 11 vehicle ID is 12 vehicle ID is 13 Car Model is A14 |
We cannot call the info() method in the lower bound, it is a restriction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class Vehicle{ private int VehicleId; public Vehicle(int vehicleId) { super(); VehicleId = vehicleId; } public int getVehicleId() { return VehicleId; } @Override public String toString() { return "Vehicle [VehicleId=" + VehicleId + "]"; } void info(){ System.out.println("vehicle ID is "+getVehicleId()); } } class Car extends Vehicle{ private String CarModel; public Car(int vehicleId, String carModel) { super(vehicleId); CarModel = carModel; } public String getCarModel() { return CarModel; } @Override public String toString() { return "Car [CarModel=" + CarModel + "]"; } @Override void info(){ System.out.println("Car Model is "+getCarModel()); } } public class App { public static void main(String[] args) { List<Vehicle> list = new ArrayList<>(); list.add(new Vehicle(10)); list.add(new Vehicle(11)); list.add(new Vehicle(12)); list.add(new Vehicle(13)); list.add(new Car(14, "A14")); display(list); } public static void display(List<? super Car> list){ for(Object element: list){ element.info(); } } } |
Output:
1 2 3 |
Compile time error: The method info() is undefined for the type object. |
Contributed by: Salim Sheikh