Generics wildcard arguments
The wildcard ?
in Java is a special actual parameter for the instantiation of generic types.
Let’s understand this with an example:
Consider two classes called CompAEmp and CompBEmp extending Emp class. We have a generic class called MyEmployeeUtil, where we have utilities to perform employee functions irrespective of which comapany emp belogns too. This class accepts subclasses of Emp. In case if we want to compare salaries of two employees, how can we do using MyEmployeeUtil class?
U can think that below sample code might work, but it wont work.
1 2 3 4 5 6 7 8 |
public boolean isSalaryEqual(MyEmployeeUtil otherEmp) { if(emp.getSalary() == otherEmp.getSalary()) { return true; } return false; } |
Because once you create an object of MyEmp class, the type argument will be specific to an instance type. So you can compare between only same object types, ie, you can comapare either objects of CompAEmp or CompBEmp, but not between CompAEmp and CompBEmp. To solve this problem, wildcard argument will helps you. Look at below sample code, which can solve your problem.
1 2 3 4 5 6 7 8 |
public boolean isSalaryEqual(MyEmployeeUtil<?> otherEmp) { if(emp.getSalary() == otherEmp.getSalary()) { return true; } return false; } |
In the above example MyEmployeeUtil> means class typed to unknown type. This could be MyEmployeeUtil
Here is complete program 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 63 64 65 66 67 68 69 70 |
package com.j2eereference.generics; public class MyWildcardEx { public static void main(String a[]) { MyEmployeeUtil empA = new MyEmployeeUtil(new CompAEmp("Ram", 20000)); MyEmployeeUtil empB = new MyEmployeeUtil(new CompBEmp("Krish", 30000)); MyEmployeeUtil empC = new MyEmployeeUtil(new CompAEmp("Nagesh", 20000)); System.out.println("Is salary same? "+empA.isSalaryEqual(empB)); System.out.println("Is salary same? "+empA.isSalaryEqual(empC)); } } class MyEmployeeUtil { private T emp; public MyEmployeeUtil(T obj) { emp = obj; } public int getSalary() { return emp.getSalary(); } public boolean isSalaryEqual(MyEmployeeUtil<?> otherEmp) { if(emp.getSalary() == otherEmp.getSalary()){ return true; } return false; } } class Emp{ private String name; private int salary; public Emp(String name, int sal){ this.name = name; this.salary = sal; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } } class CompAEmp extends Emp{ public CompAEmp(String nm, int sal){ super(nm, sal); } } class CompBEmp extends Emp{ public CompBEmp(String nm, int sal){ super(nm, sal); } } |
Bounded Wildcard:
A bounded wildcard is one with either an upper or a lower constraint. Wildcard can be constrained if one doesn’t want to be compatible with all instantiations:
Generic extends SubtypeOfUpperBound>
This reference can hold any instantiation of Generic
with an actual type parameter of SubtypeOfUpperBound
‘s subtype. A wildcard that does not have a constraint is effectively the same as one that has the constraint extends Object
, since all types implicitly extend Object. A constraint with a lower bound
Generic super SubtypeOfUpperBound>
can hold instantiations of Generic
with any supertype (e.g. UpperBound
) of SubtypeOfUpperBound
.
Example:
In the Java Collections Framework, the class List
represents an ordered collection of objects of type MyClass
. Upper bounds are specified using extends
: AList extends MyClass>
is a list of objects of some subclass of MyClass
, i.e. any object in the list is guaranteed to be of type MyClass
, so one can iterate over it using a variable of type MyClass
1 2 3 4 5 6 7 |
public void doSomething(List<? extends MyClass> list) { for(MyClass object:list) { // do something } } |
However, it is not guaranteed that one can add any object of type MyClass
to that list:
1 2 3 4 5 |
public void doSomething(List<? extends MyClass> list) { MyClass m = new MyClass(); list.add(m); // Compile error } |
The converse is true for lower bounds, which are specified using super
: A List super MyClass>
is a list of objects of some superclass of MyClass
, i.e. the list is guaranteed to be able to contain any object of type MyClass
, so one can add any object of type MyClass
:
1 2 3 4 5 |
public void doSomething(List<? super MyClass> list) { MyClass m = new MyClass(); list.add(m); // OK } |
However, it is not guaranteed that one can iterate over that list using a variable of type MyClass
:
1 2 3 4 5 6 7 |
public void doSomething(List<? super MyClass> list) { for(MyClass object : list) // Compile error { // do something } } |
In order to be able to do both add objects of type MyClass
to the list and iterate over it using a variable of type MyClass
, a List<MyClass>
is needed, which is the only type of List
that is both List<? extends MyClass>
and List<? super MyClass>
.
Important points on Wildcard
1. Consider following program:
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 |
import java.util.ArrayList; import java.util.List; public class WildCardNumber { public static void main(String[] args) { List le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(50));// * Compile time error ln.add(new EvenNumber(46)); // ** Compile time error } } class NaturalNumber { private int n; public NaturalNumber(int n) { this.n = n; } } class EvenNumber extends NaturalNumber { public EvenNumber(int n) { super(n); } } |
Why compile time error..?
Answer is:
The reason it’s fobidden is because if it was allowed:
1 2 3 4 5 |
List le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(50)); ln.add(new EvenNumber(46)); EvenNumber even = le.get(0); // ClassCastException |
We are guaranteed by le declaration that all numbers must be even numbers. But if you’re allowed to add a NaturalNumber there then this guarantee breaks.
2. How WildCard works
1 2 3 4 5 6 7 |
void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e); } } |
1 2 3 4 5 |
void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e); } } |
A Collection<Object>
can contain Object
and subclasses of it, and since everything (including String
) is a subclass of Object
, you can add anything to such a collection. However, you cannot make any assumptions about its contents except that they’re Object
s.On the other hand, A Collection<?>
contains only instances of a specific unknown type and its subclasses, but since you don’t know which type it is, you cannot add anything except null.
Leave a Reply