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

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top