I have a newbie question.
interface Animal {
void partner(Animal other);
}
class Lion implements Animal {
int areaUnderControl;
@Override
public void partner(Animal other) {
Lion lion = (Lion) other;
this.areaUnderControl += lion.areaUnderControl;
}
}
class Human implements Animal {
int money;
@Override
public void partner(Animal other) {
Human human = (Human) other;
this.money += human.money;
}
}
I want that the partner method in the concrete implementations only receive parameters of its type or its subtype. One solution is to use generics to define Animal as:
interface Animal<T extends Animal<T>> {
void partner(T other);
}
class Lion implements Animal<Lion> {
int areaUnderControl;
@Override
public void partner(Lion other) {
this.areaUnderControl += other.areaUnderControl;
}
}
class Human implements Animal<Human> {
int money;
@Override
public void partner(Human other) {
this.money += other.money;
}
}
However someone can still abuse this as
class Bear implements Animal<Lion> {
int honey;
@Override
public void partner(Lion other) {
this.honey += other.areaUnderControl; //Problem
}
}
where a Bear is aspiring to partner with Lion. Is there someway that I can tie it to the type and subtypes of this
I tried searching through the existing questions and somehow I feel the design of the interface itself may be flawed. If someone can point out why this is wrong or the right way to achieve this, it would be greatly appreciated.
Thanks!
There is no way to enforce this at the interface level, because there is no reason to do so from a type safety point of view. So what if it's possible to define a class Bear implements Animal<Lion>
? By itself, it's type safe (i.e. will not throw unexpected ClassCastException, etc.) -- that's the whole point that Generics is for.
In fact, from a type safety point of view, most likely you just need interface Animal<T>
(analogous with interface Comparable<T>
). Yes, this will allow someone to define strange classes that implement Animal with a type parameter that is a completely unrelated class. So what? There is nothing wrong with that per se.
In the places that use this interface, they can enforce the relationship that you want, like public <T extends Animal<? super T>> void someMethodThatUsesAnimal(T animal)
. Because it is only in the places that use it that really cares whether the partner
method takes itself as a parameter. This is analogous to how Comparable
is used (e.g. public <T extends Comparable<? super T>> void sort(List<T> list)
)
I do not think you can enforce this at compile time. Please look at the workaround which ensures this at run time.
class Tester {
public static void main(String[] args) {
SnowLion lion = new SnowLion();
// This will throw IllegalArgumentException
Bear bear = new Bear();
}
}
abstract class Animal<T extends Animal<T>> {
protected Animal(Class<T> implClazz) {
if (!implClazz.isAssignableFrom(getClass())) {
throw new IllegalArgumentException();
}
}
abstract void partner(T other);
}
class Lion extends Animal<Lion> {
public Lion() {
super(Lion.class);
}
int areaUnderControl;
@Override
public void partner(Lion other) {
this.areaUnderControl += other.areaUnderControl;
}
}
class Human extends Animal<Human> {
public Human() {
super(Human.class);
}
int money;
@Override
public void partner(Human other) {
this.money += other.money;
}
}
class Bear extends Animal<Lion> {
public Bear() {
super(Lion.class);
}
int honey;
@Override
public void partner(Lion other) {
this.honey += other.areaUnderControl; // Problem
}
}
class SnowLion extends Lion {
int snowAreaUnderControl;
public SnowLion() {
super();
}
@Override
public void partner(Lion other) {
this.snowAreaUnderControl += other.areaUnderControl;
}
}
I got this idea from Java generics to enforce return type of abstract method
Hope this helps.
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