Bounded TypesS2C Home « Bounded Types

Generics are by design invariant, which means for any two distinct types Type1 and Type2, List<Type1> is neither a supertype or subtype of List<Type2>. The following code snippet should help with understanding of this statement:


package com.server2client;
/*
  Generics are invariant
*/
List<Object> aList = new ArrayList<String>(); // Invalid
List<String> aList = new ArrayList<String>(); // Valid

You would think that because String like all objects extends Object that the first statement is prefectly valid, but generics doesn't work like polymorphic types because of invariance. So the simple rule here is that the reference type must always match the actual object type when using generics.

Of course we could use the <> (Diamond) Operator for the actual type parameter above, so a String is instantiated through inference, but we are just making a simple point on invariance for the rest of the lesson and there are situations when we may want to use a superclass. Say for instance we want to create a generic class that uses subclasses of the Number class to do some mathematical calculations. How do we get the different types to work? First lets look at generic invariance by writing a class:


package com.server2client;

/*
  Generic type invariance
*/
public class BoundedType<T> {

    // Generic object declaration
    private final T genericNumberObj;

    // Pass reference to object of type T to our constructor
    public BoundedType(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();
    }
}

Building the BoundedType class produces the following output:

Build bounded type class
Screenshot 1. Building the BoundedType class.

The compiler has no way of knowing we are only passing numeric types and we want to use subclasses of Number, in fact all the compiler can do is assume <T> is of type Object and when doing this the build fails as there is no .intValue() or .doubleValue() method associated with the class.

Luckily for us Java generics come with bounded types which allows us to specify a superclass boundary which our formal type parameters must be or extend from. So in our case we need to change our generic type from <T> to <T extends Number>. This is now telling the compiler that our formal type parameter of <T> must either be a Number or a subclass thereof. Lets modify our class declaration to do this:


package com.server2client;
/*
  Generic type invariance
*/
public class BoundedType<T extends Number> {  // Bounded Type

    // Generic object declaration
    private final T genericNumberObj;

    // Pass reference to object of type T to our constructor
    public BoundedType(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();
    }
}

The program builds fine now and will only allow subclasses of the Number class to use our class.

Lets write a test class to show how we can pass various subclasses of Number to our BoundedType class and nothing else:


package com.server2client;

/*
  Test our BoundedType class that only accepts Number subclasses
*/
public class TestBoundedType {

    public static void main(String[] args) {

        // Test the BoundedType using an Integer object
        BoundedType<Integer> genIntegerObj = new BoundedType<>(12);
        System.out.println("Square of genIntegerObj is: " + genIntegerObj.squareInteger());
        System.out.println("Fractional Square of genIntegerObj is: " + genIntegerObj.squareDouble());

        // Test the BoundedType using a Double object
        BoundedType<Double> genDoubleObj = new BoundedType<>(12.12);
        System.out.println("Square of genDoubleObj is: " + genDoubleObj.squareInteger());
        System.out.println("Fractional Square of genDoubleObj is: " + genDoubleObj.squareDouble());

        // Test the BoundedType using a String object
        BoundedType<String> genStringObj = new BoundedType<>("A stitch in time");
        System.out.println("Square of genStringObj is: " + genStringObj.squareInteger());
        System.out.println("Fractional Square of genStringObj is: " + genStringObj.squareDouble());
    }
}

Building the TestBoundedType class produces the following output:

Build test bounded type class
Screenshot 2. Building the TestBoundedType class.

As you can see from the screenshot we got a compiler error for the String object as this doesn't extend Number which is what we would expect.

Below is the code after removing the comment and last three statements for the String invocation of the BoundedType class from the main() method.


package com.server2client;

/*
  Test our BoundedType class that only accepts Number subclasses
*/
public class TestBoundedType {

    public static void main(String[] args) {

        // Test the BoundedType using an Integer object
        BoundedType<Integer> genIntegerObj = new BoundedType<>(12);
        System.out.println("Square of genIntegerObj is: " + genIntegerObj.squareInteger());
        System.out.println("Fractional Square of genIntegerObj is: " + genIntegerObj.squareDouble());

        // Test the BoundedType using a Double object
        BoundedType<Double> genDoubleObj = new BoundedType<>(12.12);
        System.out.println("Square of genDoubleObj is: " + genDoubleObj.squareInteger());
        System.out.println("Fractional Square of genDoubleObj is: " + genDoubleObj.squareDouble());
    }
}

Running the amended TestBoundedType class produces the following output:

Run test bounded type class
Screenshot 3. Running the amended TestBoundedType class.

So from the screenshot you can see that our BoundedType class works with subclasses of the Number object due to our use of bounded types.

Related Quiz

Generics Quiz 5 - Bounded Types Quiz

Lesson 5 Complete

In this lesson we looked at generic bounded types and how to use them.

What's Next?

In the next lesson we look at generic unbounded wildcard types.