Understanding bounded generics in java. What is th

2020-02-29 03:05发布

问题:

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?

回答1:

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);
}