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?