开发者

How do I fix this Java Generics wildcard/abstract ambiguity problem?

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);
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜