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?
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.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With