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:
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:
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:
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.