Java Generics – quick question on Greater Than Method with Objects

Tags: ,



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?
}

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 subtype Value
  • 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 general Vehicle, 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).


Source: stackoverflow