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).