user515655
user515655

Reputation: 997

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?

Upvotes: 1

Views: 2585

Answers (2)

Ahmed Mazher
Ahmed Mazher

Reputation: 333

as JB Nizet say

I'm afraid you'll have to make it a run-time check, or to create an adapter class that wraps IceCream and implements WillFitInMiniFoodFreezer.

this will solve your problem until java solve its generic nonsense

you suppose the following are in some third party library

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

    }
    //third party class that has function require type implementing the three interfaces
    class SomeClass{

    <T extends Edible & SmallerThanABeachball & Freezeable> void doSomething(T someThing){
        System.out.println(someThing);
    }

}

you want to make your own storage that accept types implementing the three interfaces Edible, SmallerThanABeachball, Freezeable

OK not beautiful one but a simple work around

create interface that implement this interfaces

and create a class that do nothing but extending the target class and implementing the new combining interface

    interface WillFitInMiniFoodFreezer extends Edible, SmallerThanABeachball, Freezeable {
    }

    class MiniFoodFreezer extends StorageChest<WillFitInMiniFoodFreezer> {
    }
    //create your one version of iceCream implement the combining interface

    class MyIceCream extends IceCream implements WillFitInMiniFoodFreezer {
    }
    //you can make new types implementing WillFitInMiniFoodFreezer

    class Potato implements WillFitInMiniFoodFreezer {
    }

    class FreezerTest {

        public FreezerTest() {
            MiniFoodFreezer freezer = new MiniFoodFreezer();
            //what you have wished
            freezer.add(new Cat());//DESIRE COMPILE ERROR
            freezer.add(new MyIceCream());//DESIRE OK
            freezer.add(new Potato());//DESIRE OK
            freezer.add(new MyBrother());///DESIRE COMPILE ERROR
            freezer.add(new Banana());//DESIRE COMPILER ERROR
            freezer.add(new Marbles());//DESIRE COMPILER ERROR
    //third party function parametrized with the three interfaces
    SomeClass thirdPartyClass = new SomeClass();
        thirdPartyClass.doSomething(new MyIceCream()); // ^_^ ok
        thirdPartyClass.doSomething(new Potato()); // ^_^ ok
        thirdPartyClass.doSomething(new Cat());//:( not ok
        }

    }//end 

Upvotes: 0

Paul Bellora
Paul Bellora

Reputation: 55233

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
}

Upvotes: 3

Related Questions