user515655 user515655 - 4 months ago 13
Java Question

How to pass multiple generic parameters when extending a generic class

The following will not compile but I wish to create something of similar functionality which will compile:

public class FreezerTest
{
interface Edible{}
interface SmallerThanABeachball{}
interface Freezeable{}
abstract class BoxedItem
{}
class Marbles extends BoxedItem
{}
class IceCream extends BoxedItem implements Freezeable, SmallerThanABeachball, Edible
{}
class MyBrother
{}
class Banana implements Edible, SmallerThanABeachball
{}
class Cat implements SmallerThanABeachball
{}

abstract class StorageChest<T>{
public void add(T toStore){}
}

class MiniFoodFreezer extends StoreageChest<Freezeable & Edible & SmallerThanABeachball>{
}

public FreezerTest(){
MiniFoodFreezer freezer = new MiniFoodFreezer();
freezer.add(new Cat());//DESIRE COMPILE ERROR
freezer.add(new IceCream());//DESIRE OK
freezer.add(new MyBrother());///DESIRE COMPILE ERROR
freezer.add(new Banana());//DESIRE COMPILER ERROR
freezer.add(new Marbles());//DESIRE COMPILER ERROR
}
}//end


One thought was to create an all-encompassing interface and then pass that:

interface WillFitInMiniFoodFreezer extends Edible, SmallerThanABeachball, Freezeable{}
class MiniFoodFreezer extends StorageChest<WillFitInMiniFoodFreezer>{
}


...however what if Edible, SmallerThanABeachball, and Freezeable are all from a 3rd party library and other third-party libraries refer to these types, some of which have the interface implementations necessary meet the criteria for WillFitInMiniFoodFreezer but do not explicitly implement WillFitInMiniFoodFreezer?

Answer

The issue here is that Freezeable & Edible & SmallerThanABeachball is not itself a type - the ampersand (&) can only be used to define multiple upper bounds when declaring a type parameter, for example <T extends Freezeable & Edible & SmallerThanABeachball>. This language limitation is further discussed here: How to reference a generic return type with multiple bounds

One workaround is to use a combination of composition and a generic add method:

class Freezer extends StoreageChest<Freezeable> { }

class MiniFoodFreezer {

    private final Freezer freezer = new Freezer();

    public <T extends Freezeable & Edible & SmallerThanABeachball> void add(
            final T toStore
    ) {
        freezer.add(toStore);
    }
}

The downside being that MiniFoodFreezer no longer is-a StoreageChest of anything, so you lose any direct benefits of inheritance. However, you can expose differently typed views of the same objects as needed. For example, assume StoreageChest<T> implements Iterable<T>:

class MiniFoodFreezer {

    private final Freezer freezer = new Freezer();

    public <T extends Freezeable & Edible & SmallerThanABeachball> void add(
            final T toStore
    ) {
        freezer.add(toStore);
    }

    public Iterable<Freezeable> asFreezables() {
        return freezer;
    }

    public Iterable<Edible> asEdibles() {
        // this is okay because add must take an Edible and Iterable is read-only
        @SuppressWarnings("unchecked")
        final Iterable<Edible> edibles = (Iterable<Edible>)(Iterable<?>)freezer;
        return edibles;
    }

    public Iterable<SmallerThanABeachball> asSmallerThanBeachballs() {
        // same reasoning as above
        @SuppressWarnings("unchecked")
        final Iterable<SmallerThanABeachball> smallerThanBeachballs =
                (Iterable<SmallerThanABeachball>)(Iterable<?>)freezer;
        return smallerThanBeachballs;
    }
}

Then we can do:

final MiniFoodFreezer miniFoodFreezer = new MiniFoodFreezer();
miniFoodFreezer.add(new IceCream());
miniFoodFreezer.add(new SnoCone());
miniFoodFreezer.add(new Slushy());

for (final Freezeable freezable : miniFoodFreezer.asFreezables()) {
    // do freezable stuff
}

for (final Edible edible : miniFoodFreezer.asEdibles()) {
    // do edible stuff
}

for (
        final SmallerThanABeachball smallerThanABeachBall :
        miniFoodFreezer.asSmallerThanBeachballs()
) {
    // do smaller-than-a-beach-ball stuff
}