问题
Here is a piece of code I'm working on. I have a Callback object that can return a specified super type. Vehicle
is one of these classes here. These classes are used as supertype for other classes like Car
and Train
here.
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
}
});
}
}
Here I get ClassCastException as Vehicule cannot be directly casted to Car or Train. I would like to either call the execute
giving it a type like Car
or Train
like so :
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
}
});
}
}
I'm trying to do this because in my case, Vehicle
contains a map of attribute and a Car
a vehicle that has some key-value pair in that list. Train has others. My Car
and Train
object provides easy getter that knows the specific keys to retrieve data from the Map
.
The only way I was able to do it was to remove the parent-child relation and build a decorator around a Vehicle
for my Train
and Car
classes.
回答1:
The problem is that you have a mechanism that should work for any Vehicle, without distinguishng between Car and Train - yet you want different behaviour for Car and Train.
So there are several solutions:
1) Split up the mechanism so you have different, typed, callbacks for Car and Train
2) Use polymorphism - provide method(s) in Vehicle that you over-ride differently in Car and Train, and call these in the callbacks.
3) If all else fails, use instanceof
to detect the actual type of the object before casting it to Car or Train.
回答2:
As is, you are asking for new Vehicle()
to produce a Car
- no compiler or cast will be able to make this work.
What you probably wanted instead is this:
interface CompletionCallback<C> {
Class<C> getType();
void onCompletion(C object);
}
where you would need to check types before calling the callback:
for (CompletionCallback<?> c : callBacks) {
if (c.getType().isAssignableFrom(vehicle)) {
// Perform some ugly casting here
}
}
OR you could type-check in your callback using instanceof
.
Javas EventListenerList
does something similar, too. Again, the inner code involves ugly casting. But the API is nice, because you would register the callback with a type:
addCallback(Car.class, new Callback<Car>() {...});
addCallback(Submarine.class, new Callback<Submarine>() { ... });
if I recall correctly, it stores the class and callback in an Object[]
array internally, and does use casting. But this pattern is nice: register with the type.
回答3:
You can test using instanceof
or YourClass.class.isInstance(OtherClass);
Perhaps you could create an interface for getting your required values and test for that.
来源:https://stackoverflow.com/questions/28814115/java-generic-to-avoid-classcastexception