The code below is a simplified version of the pattern my project is using. The standard pattern we use is to have a Writer for each object type. For the subtypes of one abstract type (in this example Animal), I'd like an enum to serve as a lookup for the correct writer.
abstract class Writer<T> {
abstract void write(T value);
}
abstract class Animal {
abstract AnimalType getType();
}
class Cat extends Animal {
AnimalType getType() { return AnimalType.CAT; }
}
class CatWriter extends Writer<Cat> {
void write(Cat value) { }
}
// The AnimalType stores a reference to the correct writer for the Animal subclass
enum AnimalType {
CAT(new CatWriter());
Writer<? extends Animal> writer;
Writer writerThatWorksWithWarning;
Writer<Animal> writerThatWorksButCantBeAssigned;
AnimalType(Writer<? extends Animal> writer) {
this.writerThatWorksWithWarning = writer;
this.writer = writer开发者_如何学运维;
// ERROR: Incompatible Types
this.writerThatWorksButCantBeAssigned = writer;
}
}
Sample use case:
class Test {
public static void main(String... args) {
Animal value = new Cat();
// ERROR: write (capture<? extends Animal) in Writer cannot be applied to (Animal)
value.getType().writer.write(value);
// WARNING: Unchecked call
value.getType().writerThatWorksWithWarning.write(value);
// This line works fine here - but can't be assigned above
value.getType().writerThatWorksButCantBeAssigned.write(value);
}
}
I think that my problem is similar to the problem in this question: Java Generics with wildcard, however I can't tell how to solve it.
I've put the inline errors and warnings I get in the comments.
Any ideas?
I think the issue here is that you can't represent a type hierarchy with an enum, so there's no way to tell the type system that for enum { CAT, DOG; }
the CAT
should type to CAT extends Animal
and the DOG
types to DOG extends Animal
. So But since you have a class hierarchy already, why not use that? i.e. something like :
public interface Writer<T> {
public void write(T t);
}
public abstract class Animal<T extends Animal<T>> {
public abstract Writer<T> getWriter()...
}
public class Cat extends Animal<Cat> {
@Override
public Writer<Cat> getWriter()...
}
It seems to me that what you're really using the enum for is something more like a hashmap of <Class, Writer<Class>>
, sort of a built in singleton. You can do this, but only by hiding the types.
I would have animals unaware of writers. they are animals after all.
You can have a Map<Class,Writer>
, and for each entry in it, you maintain that the key Class<X>
and value Writer<X>
are about the same type X
. We can't express that relation in types, so casts must be done at some places. If looking up fails for a type (say Cat), try looking up again with its super types (Animal)
A type safe public API can be designed like
static public <T> void registerWriter(Class<T> type, Writer<T> writer)
static public <T> Writer<? super T> getWriter(Class<T> type)
Suppose we don't have a Writer directly mapped to Cat, but we do have a Writer<Animal>
for Animal, then that writer will be returned for Cat.class. That is ok, because that writer does accept all animals.
This convenient method can be provided:
static public static void write(Object obj)
from the type of the object, a suitable writer can be found, and the writer will accept the object.
Try this instead,
enum AnimalType {
CAT(new CatWriter());
private Writer<? extends Animal> writer;
AnimalType(Writer<? extends Animal> writer) {
this.writer = writer;
}
public Writer<Animal> getWriter() {
return (Writer<Animal>)writer;
}
}
Moreover, I am not sure what are you up to. But I believe that Visitor pattern will come handy in this case.
Problem with the above solution, the code below will break the thing.
Animal cat = new Cat();
Animal dog = new Dog();
cat.getType().getWriter().write(cat);
// java.lang.ClassCastException in the write() method's argument
cat.getType().getWriter().write(dog);
精彩评论