Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using java generics to ensure that argument received is same as class or subtype thereof

Tags:

java

generics

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!

like image 747
Kilokahn Avatar asked Jul 11 '12 06:07

Kilokahn


2 Answers

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))

like image 178
newacct Avatar answered Nov 15 '22 14:11

newacct


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.

like image 20
krishnakumarp Avatar answered Nov 15 '22 14:11

krishnakumarp