Unbounded Wildcard TypeJ8 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 info.java8;

/*
  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:

Build unbounded type class
Screenshot 1. Building the UnboundedType class.

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 info.java8;

/*
  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:

Build test unbounded type class
Screenshot 2. Building the TestUnboundedType class.

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 info.java8;

/*
  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:

Run unbounded type class
Screenshot 3. Running the UnboundedType class.

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.

Class literal Examples
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 info.java8;

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:

Run InstanceofTest class
Screenshot 4. Building the InstanceofTest class.

Below is the amended code after changing the instanceof operator comparison to an unbounded wildcard type.


package info.java8;

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:

Run InstanceofTest class
Screenshot 5. Running the InstanceofTest class.

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 Type Quiz

Lesson 6 Complete

In this lesson we looked at the generic unbounded wildcard type and how to use it within our classes.

What's Next?

In the next lesson we investigate generic upper bounded wildcard types.