Search code examples
javagenericsparameterized-typesbounded-types

Understanding bounded generics in java. What is the point?


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?


Solution

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