Understanding bounded generics in java. What is the point?
Asked Answered
B

1

6

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?

Bombazine answered 17/5, 2015 at 22:53 Comment(1)
You are right. Stupid example. Generics don't make any sense in this case. And the fact that Integer is final makes it even worse!Fioritura
J
13

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);
}
Jeaz answered 17/5, 2015 at 23:3 Comment(3)
This is a really good explanation for this example. One nitpick: "T must be at most Number" may read clearer as "T must be either Number or a subclass of Number".Skill
A new idea, concept or method to be clear must be contrasted with existing methods and tools. I don't see something like that in this answer. For example if we adopted the approach with the type we wanted to the constructor such as the example of the question (instead of using generic) then: Integer i = e.getNum() has to be changed with type casting: Integer i = (Integer) e.getNum() because compiler do not know what subtype of Number is the e.getNum().Area
Please also add an example of a comma separated bound.Gardas

© 2022 - 2024 — McMap. All rights reserved.