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?