Java 通用以避免 ClassCastException

Java generic to avoid ClassCastException

这是我正在处理的一段代码。我有一个可以 return 指定超类型的回调对象。 Vehicle 是其中之一 类。这些 类 用作其他 类 的超类型,例如 CarTrain

interface CompletionCallBack<Result> {
    void onCompletion(Result result);
}

class Vehicle {
    Map<String, Object> attributes;
}

class Car extends Vehicle {
    public String getMake(){
        return attributes.get("make").toString();
    }
}

class Train extends Vehicle {
    public String getMake(){
        return attributes.get("size").toString();
    }
}

public class Main {
    static void execute(CompletionCallBack<Vehicle> callBack) {
        callBack.onCompletion(new Vehicle());
    }

    public static void main(String[] args) {
        execute(new CompletionCallBack<Vehicle>() {
            @Override
            public void onCompletion(Vehicle result) {
                Car car = (Car) result; //Exception
            }
        });
        execute(new CompletionCallBack<Vehicle>() {
            @Override
            public void onCompletion(Vehicle result) {
                Train train = (Train) result; //Exception

            }
        });
    }
}

这里我得到 ClassCastException,因为 Vehicule 不能直接转换为 Car 或 Train。我想调用 execute 给它一个像 CarTrain 这样的类型:

public class Main {

    static void executeGeneric(CompletionCallBack<? extends Vehicle> callBack) {
        callBack.onCompletion(new Vehicle()); //compilation error
    }

    public static void main(String[] args) {
        executeGeneric(new CompletionCallBack<Car>() {
            @Override
            public void onCompletion(Car car) {
                //I receive a car here
            }
        });
    }
}

我正在尝试这样做,因为在我的例子中,Vehicle 包含一个属性映射和一个 Car 车辆,该车辆在该列表中有一些键值对。火车还有其他人。我的 CarTrain 对象提供简单的 getter,它知道从 Map.

检索数据的特定键

我能够做到的唯一方法是删除父子关系并围绕 Vehicle 为我的 TrainCar [=37= 构建装饰器].

您可以使用 instanceofYourClass.class.isInstance(OtherClass); 进行测试,也许您可​​以创建一个接口来获取所需的值并进行测试。

问题是您有一种适用于任何车辆的机制,无需区分汽车和火车 - 但您希望汽车和火车有不同的行为。

所以有几种解决方法:

1) 拆分机制,以便您对汽车和火车有不同的、类型化的回调

2) 使用多态性 - 在 Vehicle 中提供您在 Car 和 Train 中以不同方式覆盖的方法,并在回调中调用这些方法。

3) 如果所有其他方法都失败了,请使用 instanceof 检测对象的实际类型,然后再将其投射到汽车或火车。

照原样,您要求 new Vehicle() 生成 Car - 任何编译器或转换都无法完成此工作。

您可能想要的是:

interface CompletionCallback<C> {
  Class<C> getType();

  void onCompletion(C object);
}

在调用回调之前需要检查类型的位置:

for (CompletionCallback<?> c : callBacks) {
  if (c.getType().isAssignableFrom(vehicle)) {
    // Perform some ugly casting here
  }
}

或者您可以使用 instanceof.

在您的回调中进行类型检查

Javas EventListenerList 也做了类似的事情。同样,内部代码涉及丑陋的转换。但是 API 很好,因为您可以使用以下类型注册回调:

addCallback(Car.class, new Callback<Car>() {...});
addCallback(Submarine.class, new Callback<Submarine>() { ... });

如果我没记错的话,它在内部将 class 和回调存储在 Object[] 数组中,并且确实使用了转换。但是这个模式很好:用类型注册。