Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we dynamically invoke a Java interface method with a generic parameter?

Tags:

java

generics

The following code compiles and works fine. I defined a Builder interface, then the CarBuilder class is used to handle anything related to a Car, and the BusBuilder class is used to handle anything related to a Bus. Car and Bus share an abstract class named Vehicle. The code is straightforward. The code will output:

do something to CREATE the Car
do something to UPDATE the Car
do something to CREATE the Bus
do something to UPDATE the Bus

Here is the original code that compiles:

public abstract class Vehicle { }
public class Car extends Vehicle { }
public class Bus extends Vehicle { }

public interface Builder<V extends Vehicle> {
    public V createVehicle(String spec);
    public void updateVehicle(String spec, Vehicle vehicle);
}

public class CarBuilder implements Builder<Car> {
    public Car createVehicle(String spec) {
        Car car = new Car();
        System.out.println("do something to CREATE the Car");
        return car;
    }
    public void updateVehicle(String spec, Vehicle vehicle) {
        Car car = (Car) vehicle;
        System.out.println("do something to UPDATE the Car");
        return;
    }
}

public class BusBuilder implements Builder<Bus> {
    public Bus createVehicle(String spec) {
        Bus bus = new Bus();
        System.out.println("do something to CREATE the Bus");
        return bus;
    }
    public void updateVehicle(String spec, Vehicle vehicle) {
        Bus bus = (Bus) vehicle;
        System.out.println("do something to UPDATE the Bus");
        return;
    }
}

@Test
public void main() {
    Builder<? extends Vehicle> builder = null;
    Vehicle vehicle = null;

    builder = new CarBuilder();
    vehicle = builder.createVehicle("my original Car spec");
    builder.updateVehicle("my modified Car spec", vehicle);

    builder = new BusBuilder();
    vehicle = builder.createVehicle("my original Bus spec");
    builder.updateVehicle("my modified Bus spec", vehicle);
}

But, I'd like to make my code even more strongly-typed. I want to change the interface method,

from

public void updateVehicle(String spec, Vehicle vehicle);

to

public void updateVehicle(String spec, V vehicle);

In other words, I tried to use generic V instead of base class Vehicle in my interface signature. This forces the implementors of Builder to "close" on the specific product type (i.e. either Car or Bus, but not the base class Vehicle). CarBuilder shall only handle a Car; BusBuilder should only handle a Bus.

The code becomes as follows:

public interface Builder<V extends Vehicle> {
    public V createVehicle(String spec);
    public void updateVehicle(String spec, V vehicle);
}

public class CarBuilder implements Builder<Car> {
    public Car createVehicle(String spec) {
        Car car = new Car();
        System.out.println("do something to CREATE the Car");
        return car;
    }
    public void updateVehicle(String spec, Car car) {
        System.out.println("do something to UPDATE the Car");
        return;
    }
}

public class BusBuilder implements Builder<Bus> {
    public Bus createVehicle(String spec) {
        Bus bus = new Bus();
        System.out.println("do something to CREATE the Bus");
        return bus;
    }
    public void updateVehicle(String spec, Bus bus) {
        System.out.println("do something to UPDATE the Bus");
        return;
    }
}

@Test
public void main() {
    Builder<? extends Vehicle> builder = null;
    Vehicle vehicle = null;

    builder = new CarBuilder();
    vehicle = builder.createVehicle("my original Car spec");
    builder.updateVehicle("my modified Car spec", vehicle);  <== compilation error

    builder = new BusBuilder();
    vehicle = builder.createVehicle("my original Bus spec");
    builder.updateVehicle("my modified Bus spec", vehicle);  <== compilation error
}

Two compilation errors - Java compiler won't allow this because "The method updateVehicle(String, capture#3-of ? extends Vehicle) in the type Builder is not applicable for the arguments (String, Vehicle)".

In Java is there a way to do what I want to accomplish? In C# I can simply type my variable with var keyword, like var vehicle instead of Vehicle vehicle. I heard that Java generics invocation is a compile-time decision. Is there a technique to overcome this?

like image 861
Matt Wang Avatar asked May 20 '13 19:05

Matt Wang


People also ask

Can interface methods have parameters java?

When a class implements an interface it implements all of the methods declared in that interface. You can have variables and parameters of an interface type.

Can we give method body in interface?

There can be only abstract methods in the Java interface, not the method body. It is used to achieve abstraction and multiple inheritance in Java. In other words, you can say that interfaces can have abstract methods and variables. It cannot have a method body.

Can we call method using interface name?

In order to call an interface method from a java program, the program must instantiate the interface implementation program. A method can then be called using the implementation object.


1 Answers

There is an idiom, called a capture helper, that helps in cases like this.

Essentially, given your declaration of builder, all the compiler knows is that vehicle is something that extends Vehicle. Further, it knows that Builder requires some specific type of Vehicle passed to its updateVehicle() method. The compiler is unable to draw the inference that is obvious to us—that the type is compatible. To do that, we have to get rid of the wildcard by using a helper method.

A practical application to real code is not obvious from your simple test harness. But, applying it in this context would look something like the following. Hopefully this example will be enough to illustrate the idea and help you apply it to real code.

public void main()
{
  Builder<? extends Vehicle> builder;
  Vehicle vehicle;

  builder = new CarBuilder();
  vehicle = createAndUpdateHelper(builder, "my original Car spec", "my modified Car spec");

  builder = new BusBuilder();
  vehicle = createAndUpdateHelper(builder, "my original Bus spec", "my modified Bus spec");
}

private static <V extends Vehicle> V createAndUpdateHelper(Builder<V> builder, String s1, String s2)
{
  V v = builder.createVehicle(s1);
  builder.updateVehicle(s2, v);
  return v;
}
like image 133
erickson Avatar answered Oct 02 '22 12:10

erickson