I have a tricky problem with Java generics, or maybe I can't see the wood for the trees.
I have three classes, A, B, and C that look as follows.
abstract class A<T extends A<T>> {
abstract T sefl();
};
abstract class B<T extends B<T>> extends A<T> {
};
class C extends B<C> {
@Override
C sefl() {
return this;
}
}
Later I have different versions of B's as well as different versions of C's. Furthermore, I have a function test that should accept a list of B's (or of one of its cousins). Or in general, it should accept any list of elements that inherit from A. Unfortunately, I need to know the type of the list's elements in the function's body, i.e. the uppermost type a.self can return (or the type T). Function test looks as follows:
static <T extends A<T>> void test(List<T> list) {
for (A<T> a : list) {
@SuppressWarnings("unused")
T t = a.sefl();
}
}
Now, calling test with a list of C's works.
List<C> cs = new LinkedList<C>();
test(cs);
But
List<B> bs = new LinkedList<B>();
test(bs);
results in a warning requiring a type parameter and
List<B<?>> bs = new LinkedList<B<?>>();
test(bs);
is not valid. Where is my mistake, or how can I create a list of B's that is accepted by function test?
Some words to the motivation behind this problem. The classes A, B, and C (or Animal, Mammal, and Cat) implement a tree-like data structure, where each class extends the structure with some properties. Typically, all super-classes are abstract and you can only create instances from leaf-classes, e.g. cat. Now, the difficulty is that the classes implement a copy-on-write policy (COW), i.e. modifying an object creates and returns a new instance of itself with the modified property.
For example, let’s say all animals have an age property. You can easily define this property in Animal, and you can provide a getter method to return the age.
abstract class Animal<T extends Animal<T>> {
private int age;
public int getAge(int age) {
return age;
}
};
However, how do you define the setter method? You can write it as follows:
public abstract Animal setAge();
This requires that (at least) each non-abstract element must implement the setter function. For example:
class Cat extends Mammal<C> {
@Override
public Animal setAge(int age) {
return new Cat(/* .. */);
}
}
Remember, as we implement a COW policy, we must create a new instance. So, in the setter function (e.g. implement in Cat) we return a new cat with the new age. Calling cat.setAge(4) on a Cat element returns a new Cat. Unfortunately, because of the type signature, we only now that we got an Animal returned from setAge, even if we call it on a Cat directly. The twist with the generics helps to reveal the concrete type when calling setAge. So, we can construct Animal like this:
abstract class Animal<T extends Animal<T>> {
private int age;
public int getAge(int age) {
return age;
}
public abstract T setAge();
};
And in Cat we can say:
class Cat extends Mammal<C> {
@Override
public Cat setAge(int age) {
return new Cat(/* .. */);
}
}
So, back to the problem. Your right, using List<? extends Animal<?>>
as the type of the list works, but unfortunately, I need some way to know the type of the elements. Or more concrete: Function test must replace the old element with the new one. For example:
static void test2(List<? extends Animal<?>> list) {
for (Animal<?> animal : list) {
@SuppressWarnings("unused")
Animal<?> a = animal.setAge(4711);
list.add(a);
}
}
And unfortunately, the list extension list.add(a); ist the statement that doesn't work with this signature.
Well, they are two very different implementations:
class C ...
and
class B<T extends B<T>> ...
The class C
doesn't declare any generic type.
Simple letters for class names are a bit confusing here, so let's do:
abstract class Animal<T extends Animal<T>> {
abstract T sefl();
};
abstract class Mammal<T extends Mammal<T>> extends Animal<T> {
};
class Cat extends Mammal<Cat> {
@Override
Cat sefl() {
return this;
}
}
So:
List<Cat> catList = new LinkedList<>();
works well, as there is no generic type involved. The compiler determines that
Cat extends Mammal<Cat> ( == Cat extends Animal<Cat> )
fits within the bounds <T extends Animal<T>>
On the other hand for
List<Mammal> mammalList = new LinkedList<>();
test(mammalList); // ok, but mammal list of what???
the compiler can't match the bounded types.
In fact, Mammal<T extends Mammal<T>> extends Animal<T>
doesn't have anything to do with <T extends Animal<T>>
.
Even by providing a wildcard, you'll never be able to pass a List<Mammal<?>
to test
. The method signature rejects it!
A possible solution:
A more generic test method
static void test2(List<? extends Animal<?>> list) {
for (Animal<?> animal : list) {
Animal a = animal.sefl();
}
}
can be used along with different List
types:
List<? extends Mammal<?>> bs = new LinkedList<>();
test2(bs);
List<Cat> catList = new LinkedList<>();
test2(catList);
List<Animal<Cat>> animalList = new LinkedList<>();
test2(animalList);
Java version:
java 9.0.4
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With