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 layer 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.
Set<Parent>is a collection where you can add anyParentobject, and all objects you get out of it areParentobjects.Set<Child>is a collection where you can add anyChildobject, and all objects you get out of it areChildobjects.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 onlyChildobjects 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 ofParent. 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 areParentobjects.A
Set<? super Child>is a collection of some unknown supertype ofChild. This means that we can put aChildinto it, but we can’t be sure what we get out of it (apart fromObject, which is the supertype of everything).Back to your problem:
For your operations
to work, you don’t need anything more than what you already posted:
But now there can’t be store implementations which have only Childs – since you need to be able to add Parents.
You could make
Storea parameterized type instead:Then you would have
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.