I have an assignment in my Java class, we are learning Generics, I have looked in my notes, my lessons, and even on the internet and I still cant figure out what this last assignment question is asking me to do, which is:
Write a generic “greater-than” function that takes two objects as arguments, each of which has a “value” method which returns an integer and returns the argument whose “value” method returns the larger integer. Your generic function should constrain its type argument so that types without a “value” method cannot be used.
Is there a reason why “value” in quotation marks and how can a method constrain its type argument so that types without a “value” method cant be used. I’m mainly asking for clarification or maybe a little example. Am I creating a Value class and then doing something like this:
public static <T> int greaterThan(Value obj1, Value obj2){ // Value will have the "value" method? // do I compare the objects here or in the Value class? }
Advertisement
Answer
They are apparently asking you to implement a generic maxByValue
method. Since greater-than
contains a hyphen, and is thus an invalid Java identifier anyway, I’ll stick to maxByValue
.
The requirement of having a value
method is mentioned a few times, so let’s encode it as an interface:
interface Value { int value(); }
Now, the main point of having a generic parameter here is to make sure that the return type of the maxByValue
is specific enough to be useful. Let’s call this type T
. In order for the arguments to be comparable, T
must be a subtype of Value
. The only meaningful source for obtaining the return type is from the types of arguments. Putting together the three points:
- Type parameter
T
subtypeValue
- Type inferred from the arguments
- Result type is
T
gives you the signature:
public static <T extends Value> T maxByValue(T a, T b)
There are basically just two meaningful ways of implementing this. Let’s take the left-biased one (i.e. left argument is returned if value is the same):
public static <T extends Value> T maxByValue(T a, T b) { if (a.value() < b.value()) { return b; } else { return /* left as an exercise */; } }
Let’s try it on a trivial integer example:
class IntValue implements Value { final int v; public IntValue(int v) { this.v = v; } public int value() { return v; } @Override public String toString() { return "" + v; } } IntValue a = new IntValue(42); IntValue b = new IntValue(58); IntValue c = max(a, b); System.out.println(c); // prints "58"
So far so good. Let’s see how precise the type inference is:
static interface Vehicle extends Value { String doGenericVehicleSound(); } static abstract class Car implements Vehicle { public abstract String doCarSpecificSound(); public String doGenericVehicleSound() { return doCarSpecificSound(); } } static class Train implements Vehicle { public String doGenericVehicleSound() { return "tk-tk-------tk-tk--tk-tk--------------tk-tk"; } public int value() { return 10000000; } } static class Ferrari extends Car { public String doCarSpecificSound() { return "rr-rrr-rrrr-rrrrrrr-rrrrrrrrrrrrrrrrrrrr-RRRRRRRRRR"; } public int value() { return 222000; } } static class Tesla extends Car { public String doCarSpecificSound() { return "... ... ¯\_(ツ)_/¯"; } public int value() { return 50000; } } public static void main(String []args){ System.out.println(maxByValue(new Ferrari(), new Tesla()).doCarSpecificSound()); System.out.println(maxByValue(new Tesla(), new Train()).doGenericVehicleSound()); // System.out.println(maxByValue(new Tesla(), new Train()).doCarSpecificSound()); }
The point to see here is the following. We have the following subtyping relation:
Train extends Vehicle Car extends Vehicle Ferrari extends Car Tesla extends Car
and the following least upper bounds for the concrete instances:
LUB(Train, Train) = Train LUB(Train, Ferrari) = Vehicle LUB(Train, Tesla) = Vehicle LUB(Ferrari, Ferrari) = Ferrari LUB(Ferrari, Tesla) = Car LUB(Tesla, Tesla) = Tesla
(and all symmetric cases too).
Now, when we
- put two cars into
maxByValue
, we get out a car (first example), but - when we put a car and a train into
maxByValue
, we get a more generalVehicle
, so that the car-specific methods become unavailable (examples two and three; third does not compile – rightly so, because a train has no car-methods).