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