Unbounded Wildcard TypeS2C Home « Unbounded Wildcard Type
We saw how we can use a bounded type to get around the problem with invariance in the Bounded Type lesson.
Lets create an UnboundedType
class based on the BoundedType
class from the Bounded Type lesson that also compares the actual integer values of objects passed in to see if they are equal.
package com.server2client;
/*
Generic type invariance
*/
public class UnboundedType<T extends Number> { // Bounded Type
// Generic object declaration
private final T genericNumberObj;
// Pass reference to object of type T to our constructor
public UnboundedType(T genericNumberObj) {
this.genericNumberObj = genericNumberObj;
}
// Return the square of the integer
public int squareInteger() {
return genericNumberObj.intValue() * genericNumberObj.intValue();
}
// Return the square of the fraction
public double squareDouble() {
return genericNumberObj.doubleValue() * genericNumberObj.doubleValue();
}
// Return true if integer values of two objects are equal
public boolean intEqual(UnboundedType<T> obj) { // T type
return genericNumberObj.intValue() == obj.genericNumberObj.intValue();
}
}
Building the UnboundedType
class produces the following output:
The program builds fine, so now we need to write a test class to see if everything still works and we can compare integer values:
package com.server2client;
/*
Test our TestUnboundedType class that only accepts Number subclasses
*/
public class TestUnboundedType {
public static void main(String[] args) {
// Test the UnboundedType class using an Integer object
UnboundedType<Integer> genIntegerObj = new UnboundedType<>(12);
System.out.println("Square of genIntegerObj is: " + genIntegerObj.squareInteger());
System.out.println("Fractional Square of genIntegerObj is: " + genIntegerObj.squareDouble());
// Test the UnboundedType class using another Integer object
UnboundedType<Integer> genIntegerObj2 = new UnboundedType<>(13);
System.out.println("Square of genIntegerObj2 is: " + genIntegerObj2.squareInteger());
System.out.println("Fractional Square of genIntegerObj2 is: " + genIntegerObj2.squareDouble());
// Test the UnboundedType class using a Double object
UnboundedType<Double> genDoubleObj = new UnboundedType<>(12.12);
System.out.println("Square of genDoubleObj is: " + genDoubleObj.squareInteger());
System.out.println("Fractional Square of genDoubleObj is: " + genDoubleObj.squareDouble());
// Test the UnboundedType class using another Double object
UnboundedType<Double> genDoubleObj2 = new UnboundedType<>(13.13);
System.out.println("Square of genDoubleObj2 is: " + genDoubleObj2.squareInteger());
System.out.println("Fractional Square of genDoubleObj2 is: " + genDoubleObj2.squareDouble());
// Test integer equality of objects
if (genIntegerObj.intEqual(genIntegerObj2)) {
System.out.println("Integer values are equal");
} else {
System.out.println("Integer values are not equal");
}
if (genDoubleObj.intEqual(genDoubleObj2)) {
System.out.println("Integer values are equal");
} else {
System.out.println("Integer values are not equal");
}
if (genIntegerObj.intEqual(genDoubleObj)) {
System.out.println("Integer values are equal");
} else {
System.out.println("Integer values are not equal");
}
}
}
Building the TestUnboundedType
class produces the following output:
As you can see from the screenshot we got a compiler error for the call to the intEqual()
method. In the first two calls to the intEqual()
method the invoking and parameter object
are the same and so there is no problem. In the last call we have an invoking object of Integer
and a parameter object of Double
and as the compiler error points out we cant convert
the latter bounded class type to the former. We need a way to let the compiler know that the input parameter, which we know can only be instantiated as a Number
object or subclass
thereof is an unknown type. We can achieve this by using an unbounded wildcard type, which is specified using the ?
symbol, as the input parameter to the intEqual()
method of
the BoundedType
class.
Below is the amended code after changing the signature to boolean intEqual(UnboundedType<?> obj)
and recompile the UnboundedType
class.
package com.server2client;
/*
Generic type invariance
*/
public class UnboundedType<T extends Number> { // Bounded Type
// Generic object declaration
private final T genericNumberObj;
// Pass reference to object of type T to our constructor
public UnboundedType(T genericNumberObj) {
this.genericNumberObj = genericNumberObj;
}
// Return the square of the integer
public double squareInteger() {
return genericNumberObj.intValue() * genericNumberObj.intValue();
}
// Return the square of the fraction
public double squareDouble() {
return genericNumberObj.doubleValue() * genericNumberObj.doubleValue();
}
// Return true if integer values of two objects are equal
public boolean intEqual(UnboundedType<?> obj) { // Amended to unbounded wildcard type
return genericNumberObj.intValue() == obj.genericNumberObj.intValue();
}
}
Running the UnboundedType
class produces the following output:
The UnboundedType
class now works as shown in the screenshot through our use of an unbounded wildcard type.
Exceptions to Using Raw Types Top
Although we said earlier never use raw types there are a couple of exceptions to this rule and as a workaround for one of them is using an unbounded wildcard type this is a good place to look at them.
Class Literals
Generic types are not allowed in class literals, although raw types, Array
types and primitive types are. See the Java Language Specification (JLS) 15.8.2 Class Literal for more information.
Valid | Invalid |
---|---|
Set.class (raw type) Integer[].class ( Array type)double.class (primitive type) | Set<Integer>.class Set<?>.class |
The instanceof
Operator
The instanceof
operator does now allow parameterised types due to erasure and the fact that generics are deleted at compile time.
The exception to this rule is the use of an unbounded wildcard type which can be used and is the preferred approach when using generics with the instanceof
operator
Lets look at a code example:
package com.server2client;
import java.util.ArrayList;
import java.util.List;
/*
instanceof example
*/
public class InstanceofTest {
private List<String> list = new ArrayList<>();
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Bill");
names.add("Ben");
System.out.println(names.getClass().getName());
// Call non-static method using this class
new InstanceofTest().checkEquality(names);
}
public void checkEquality(List<String> arg) {
if (arg instanceof List<String>) {
list = arg;
}
if (list == arg) {
System.out.println("List copied and equal!");
}
}
}
Building the InstanceofTest
class produces the following output:
Below is the amended code after changing the instanceof
operator comparison to an unbounded wildcard type.
package com.server2client;
import java.util.ArrayList;
import java.util.List;
/*
instanceof example
*/
public class InstanceofTest {
private List<String> list = new ArrayList<>();
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Bill");
names.add("Ben");
System.out.println(names.getClass().getName());
// Call non-static method using this class
new InstanceofTest().checkEquality(names);
}
public void checkEquality(List<String> arg) {
if (arg instanceof List<?>) { // amended to unbounded wildcard type
list = arg;
}
if (list == arg) {
System.out.println("List copied and equal!");
}
}
}
Running the InstanceofTest
class produces the following output:
The InstanceofTest
class now works as shown in the screenshot through our use of an unbounded wildcard type.
Related Quiz
Generics Quiz 6 - Unbounded Wildcard Types Quiz
Lesson 6 Complete
In this lesson we looked at generic unbounded wildcard types and how to use them within our classes.
What's Next?
In the next lesson we investigate generic Upper Bounded Wildcard Type.