Jerome
Jerome

Reputation: 1215

Java generics type inference fails

I am trying to define a class which type could be a subtype of the given one if inferred but it doesn't seem to work with the default Java type inference mechanism and I do not understand why.

Here are some relevant pieces of code to illustrate the situation

public class ObjectChanged<T extends Something> implements TriggeringCondition<T> {

    private final Class<? extends T> type;
    private String featureName = null;

    protected ObjectChanged(Class<? extends T> type) {
        this.type = type;
    }

    public ObjectChanged<T> onFeature(String featureName) {
        this.featureName = featureName;
        return this;
    }

    public static <X extends Something> ObjectChanged<X> objectChanged(Class<? extends X> type) {
        return new ObjectChanged<>(type);
    }
}

Let's say I have one class called FastCar extending Car. I would like to build an object change for a FastCar, but to downcast it to TriggeringCondition<Car>.

If I write the following code it works as expected

TriggeringCondition<Car> test() {
   return objectChanged(FastCar.class);
}

But then if I call the onFeature(String) method it doesn't compile anymore and complains that my triggering condition if of type FastCar, which is not compatible with Car.

If now I define the objectChanged function like this

public static <X extends Something, Y extends X> ObjectChanged<X> objectChanged(Class<Y> type, Class<X> baseType) {
    return new ObjectChanged<>(type);
}

Then I can use this code which resolves the problem

TriggeringCondition<Car> test() {
   return objectChanged(FastCar.class, Car.class).onFeature("something");
}

I also found out I can fix the previous build issue with this syntax, but it's quite ugly imo.

TriggeringCondition<Car> test() {
   return ObjectChanged.<Car> objectChanged(FastCar.class).onFeature("test");
}

Is there a way to write the test method like this without needing an extra parameter ?

TriggeringCondition<Car> test() {
   return objectChanged(FastCar.class).onFeature("test");
}

Upvotes: 0

Views: 47

Answers (1)

Andy Turner
Andy Turner

Reputation: 140319

Is there a way to write the test method like this without needing an extra parameter ?

No.

If you don't want to use the type witness (<Car>), all you can do is to assign the objectChanged result to a variable, and then call onFeature on that variable.

TriggeringCondition<Car> test() {
   TriggeringCondition<Car> tc = objectChanged(FastCar.class);
   return tc.onFeature("test");
}

This is a problem which crops up a lot if you use Guava's Immutable*.Builders:

ImmutableList<String> list =
    ImmutableList.<String>builder()
        .add("foo")
        .build();

The type witness is needed here, otherwise the type of the Builder is inferred to be ImmutableList.Builder<Object>, because the type of the polyexpression is determined before the .add(String) call.

It's annoying, but that's the nature of the beast.


One thing you could do is to define a static upcast method:

static <T extends Something> ObjectChanged<T> upcast(ObjectChanged<? extends T> oc) {
  ObjectChanged<T> result = new ObjectChanged<>(oc.type);
  return result.onFeature("test");
}

Now you can invoke something like:

TriggeringCondition<Car> test() {
   return upcast(objectChanged(FastCar.class).onFeature("test"));
}

Upvotes: 1

Related Questions