开发者

Struggling to come up with API with interfaces and generic sets

I'm struggling to come up with an easy API due to generics not being covariant

This is my new problem: I can't get a Set<> that does what I need. I've tried reading various guides but they all use lots of buzzwords that I get lost.

Consider the following classes

public class Parent {}
public class Child extends Parent {}
interface Store {
    public Set<Parent> getParents(); //PROBLEM!! This needs to change
}

At a minimum, I need these operations to work

Set<Parent> parents = store.getParents();
parents.add(new Parent()); 
parents.add(new Child()); 
store.getParents().add(new Child()); //Note the lack of generics

for(Parent curEntry : store.getParents()) {

}

Classes that implement Store need to be able to work with Child (meaning they have a Set that is child). They need to expose the Childs as Parents though to the outside world.


Attempt #1

interface Store {
    public Set<Parent> getParents();
}

class ConcreteStore implements Store {
    Set<Child> childs;
    public Set<Parent> getParents() {
        return (Set<? extends Parent>)childs; //ERROR: inconvertible types
    }
}

Attempt #2

interface Store {
    public Set<? extends Parent> getParents();
}

class ConcreteStore implements Store {
    Set<Child> childs;
    public Set<Child> getParents() {
        return childs;
    }
}

Store store = new ConcreteStore();
Set<? extends Parent> parents = store.getParents();
parents.add(new Child()); //ERROR: cannot find symbol
parents.add(new Parent()); //ERROR: cannot find symbol. What?!

for (Parent curEntry : store.getParents()) {
}

That version, while I really like it, means that adding and removing from the Set outside of the Concrete class isn't possible. Which makes it useless. What really puzzles me is that not even adding a Parent will work.

Attempt #3

interface Store<T extends Parent> {
    public Set<T> getParents();
}

public static class ConcreteStore implements Store<Child> {
    Set<Child> childs;

    @Override
    public Set<Child> getParents() {
        return childs;
    }
}

Store store = new ConcreteStore();
Set<Parent> parents = store.getParents();
parents.add(new Parent());
parents.add(new Child());

for (Parent curEntry : store.getParents()) { //ERROR: incompatible types. Found object, requited Parent
}

Note that here I know I can do Store<Child> store = new DatabaseStore(), but due to a la开发者_如何学Pythonyer of abstraction that isn't possible and would get lost. Besides, passing around generics everywhere that you use a Parent looks ugly.

--

I'm out of ideas on what to do. Doing this is way more complex than i thought. I really need some way to get


Your problem is unsolvable in the form you pose it.

  • A Set<Parent> is a collection where you can add any Parent object, and all objects you get out of it are Parent objects.
  • A Set<Child> is a collection where you can add any Child object, and all objects you get out of it are Child objects.

As we can see, there can't be an object which implements both these interfaces: If you can add any Parent, you can't be sure to get only Child objects out of it. This means that your ConcreteStore can't say "I have only childs", but someone else is allowed to put parents in it.

The Java generics system is just made to avoid these errors - wherever the compiler barks, you are most probably doing something wrong.

  • A Set<? extends Parent> is a collection of some unknown subtype of Parent. This means that we can't put anything in it (as we don't know the right type), and all we can get out of it are Parent objects.

  • A Set<? super Child> is a collection of some unknown supertype of Child. This means that we can put a Child into it, but we can't be sure what we get out of it (apart from Object, which is the supertype of everything).

Back to your problem:

For your operations

Set<Parent> parents = store.getParents();
parents.add(new Parent()); 
parents.add(new Child()); 
store.getParents().add(new Child()); //Note the lack of generics

for(Parent curEntry : store.getParents()) {

}

to work, you don't need anything more than what you already posted:

public class Parent {}
public class Child extends Parent {}
interface Store {
    public Set<Parent> getParents();
}

But now there can't be store implementations which have only Childs - since you need to be able to add Parents.

You could make Store a parameterized type instead:

interface Store<X extends Parent> {
   public Set<X> getParents();
}

Then you would have

class ConcreteStore implements Store<Child> {
    Set<Child> childs;
    public Set<Child> getParents() {
        return childs;
    }
}

Of course, this still will not allow you to put parents in it, but now the callers can see this - and there could be another implementation which implements Store<Parent>, which would allow this.


I think there is a logical mistake here. Parent IS always someone's Child but Child IS NOT always someone's parent. Therefore, it does not make sense to extend Child from Parent. That, probably, triggers requirements for other methods that are hard to represent due to this logic reversal.

I suggest you introduce common superclass, for example, Human and then revisit your design. I bet things will get simpler.


in Attempt #2 you can't add anything to that collection. That's way you get cannot find symbol on add method.
As a fast answer: you can always use Parent[] (array) and returns will be covariant.


This should probably be a comment but my rep is too low yet ...

What is the point of creating a concrete class which can only hold Child ?

I would keep :

class ConcreteStore implements Store {
    Set<Parent> parents;
    public Set<Parent> getParents() {
        return parents;
    }
}
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜