views:

87

answers:

3

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: http://stackoverflow.com/questions/1108894/java-generics-problem-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?

+2  A: 

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.

Steve B.
Yes, I am using it like a HashMap, in fact I was going to use one until I needed the `enum` anyway, because we store an integer code for the type. So, first, it was a convenient place to put the writer, and second, to store the writer in the Cat itself would break a convention across the rest of our primary objects (that do not have a superclass to cause this problem).
Renesis
A: 

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);
Adeel Ansari
Actually, its based on the same example you provided, http://stackoverflow.com/questions/1108894/java-generics-problem-with-wildcard . The idea is, you are controlling the setter, so you can type cast in the getter without any worries.
Adeel Ansari
Ah I see, so, even though I have an unchecked cast, it shouldn't worry me because I know it will be correct?
Renesis
@Renesis: Yes, precisely. See in the example you provided. The solution is to keep the property `private`, and provide a `public setter` and a `public getter` of your choice. In order to keep it safe. Here you don't need any setter because you are using an `enum` and initialising everything right away. Hence, there is no way you get some stupid thing in there.
Adeel Ansari
Perfect! Add the `@SuppressWarnings("unchecked")` above the getter and I'm all set. Thanks!
Renesis
@Renesis: What warning are you talking about?
Adeel Ansari
then `CAT.getWritter().write(dog)` would compile without warning.
irreputable
@Adeel Ansari There is an unchecked cast warning on the writer in `getWriter`
Renesis
@Renesis: Ah okay, perhaps my IDE is not set to show me all these warnings. :)
Adeel Ansari
@irreputable: Thats perfectly fine. We do welcome anything Animal.
Adeel Ansari
it will throw class cast exception at runtime, dog->Cat
irreputable
@irreputable: Yes, you are right. But thats the problem with design. The casting didn't introduce this vulnerability. See my addendum for the suggestion.
Adeel Ansari
Having compiler warnings doesn't seem to be the optimal solution. Instead, the enum probably should be deleted.
Ricky Clarkson
@Ricky Clarkson: I just tried to fix the problem in hand. Further, I already made a suggestion in my post, about how all should be implemented.
Adeel Ansari
For all those worried about the safety because of the direct access of the `writer`, I only left out the getter for simplicity of this example. The only way to cause a class cast exception would then be if your class Cat { } had the wrong type stored inside, I.E. DOG. Also, @Ricky Clarkson, see my comment on the other answer. We need the enum anyway, because it also stores an int ID we use for persistence of the Animal Type.
Renesis
+1  A: 

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.

irreputable
I would like to propose this instead. http://www.javaranch.com/journal/200601/Journal200601.jsp#a5
Adeel Ansari
that author is very confused.
irreputable
@Adeel Ansari can you tell me why you deleted your answer? I was going to accept it.
Renesis
@irreputable I like your answer, and even though the cast Adeel Ansari proposed (in the deleted answer) fixes my code, I'm going to take a look later and implement your suggestion, and see if it jives with the rest of the code.
Renesis
@Renesis: I was getting negative votes there, may be because folks didn't understand that I actually didn't propose a new solution but just tried to quick fix the prolem in hand. However, already undeleted the post for your bliss.
Adeel Ansari