5

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?

4

3 に答える 3

4

このような場合に役立つ、キャプチャ ヘルパーと呼ばれるイディオムがあります。

基本的に、 の宣言が与えられbuilderた場合、コンパイラが知っているのは、それvehicleが を拡張するものであるということだけですVehicle。さらに、そのメソッドにBuilder特定の型をVehicle渡す必要があることを認識しています。updateVehicle()コンパイラは、私たちにとって明らかな推論、つまり型に互換性があるという推論を導き出すことができません。そのためには、ヘルパー メソッドを使用してワイルドカードを取り除く必要があります。

実際のコードへの実用的なアプリケーションは、単純なテスト ハーネスからは明らかではありません。しかし、このコンテキストでそれを適用すると、次のようになります。この例がアイデアを説明し、実際のコードに適用するのに役立つことを願っています。

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;
}
于 2013-05-20T20:09:00.303 に答える
2

あなたがやろうとしていることは意味がありません: ビルダーに Car または Bus (これで問題ありません) のみを受け入れてもらいたいのですが、Vehicle のインスタンスを与えます。防止しようとしている正確なことを実行しています。ジェネリック クラス Vehicle を受け入れます。

于 2013-05-20T19:51:46.600 に答える
0

最も簡単な方法は、予想される入力または目的の入力と一致しない入力をフィルター処理することです。

if(vehicle instanceof Car) return;

列挙型を使用して車のさまざまな側面 (タイプ、色) を制御することにより、より完全な (そして壊れにくい) 実装を作成できます。次に、単一の Builder をスイッチ付きで使用して、それらの任意の組み合わせを処理できます。(このためにいくつかのサンプル コードが書かれています。必要な場合は、お気軽にメッセージを送信してください。コピーをお送りします)

于 2013-05-20T20:01:26.890 に答える