I am trying to understand bounded types and not quite grasping the point of them.
There is an example of bounded generics on which provides this use case:
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
// ...
}
If you are going to restrict the classes that can be the parameterized type, why not just forget the parameterization all together and have:
public class NaturalNumber {
private Integer n;
public NaturalNumber(Integer n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
// ...
}
Then any class that extends/implements Integer
can be used with this class.
Also, a side question: How is T
extending Integer
in the first example when the Java Integer
class is final?
How is T extending Integer in the first example when the Java Integer class is final?
T
can only be Integer
, so the "extends" here is purely symbolic. (I'm starting with the side-note because, indeed, it's an example where generics are useless. I truly have no idea why the tutorial thinks this is an informative demonstration. It's not.)
Suppose instead that T extends Number
:
class Example<T extends Number> {
private T num;
void setNum(T num) { this.num = num; }
T getNum() { return num; }
}
So the point of generics in general, is that you can do this:
Example<Integer> e = new Example<>();
e.setNum( Integer.valueOf(10) );
// returning num as Integer
Integer i = e.getNum();
// and this won't compile
e.setNum( Double.valueOf(10.0) );
Generics are a form of parametric polymorphism, essentially it lets us reuse code with a generality regarding the types involved.
So what's the point of a bound?
A bound here means that T
must be Number
or a subclass of Number
, so we can call the methods of Number
on an instance of T
. Number
is unfortunately a generally useless base class on its own (because of precision concerns), but it might let us do something interesting like:
class Example<T extends Number> extends Number {
// ^^^^^^^^^^^^^^
...
@Override
public int intValue() {
return num.intValue();
}
// and so on
}
It's more common, for example, to find T extends Comparable<T>
which lets us do something more meaningful with T
. We might have something like:
// T must be a subclass of Number
// AND implement Comparable
Example<T extends Number & Comparable<T>>
implements Comparable<Example<T>> {
...
@Override
public int compareTo(Example<T> that) {
return this.num.compareTo(that.num);
}
}
And now our Example
class has a natural ordering. We can sort it, even though we have no idea what T
actually is inside the class body.
If we combine these concepts, that:
- generics allow the "outside world" to specify an actual type and
- bounds allow the "inside world" to use a commonality,
we could build constructs such as:
static <T extends Comparable<T>> T min(T a, T b) {
return (a.compareTo(b) < 0) ? a : b;
}
{
// returns "x"
String s = min("x", "z");
// returns -1
Integer i = min(1, -1);
}