0

Trying to understand Generics with Consumer.

class Fruit{}
class Apple extends Fruit{}
class Orange extends Fruit{}

class Util{
    private Collection<Apple> appleList = new ArrayList<>();

    public Util(){
        acceptFruit(ap -> appleList.add(ap);
    }

    public <T extends Fruit> void acceptFruit(Consumer<T> fruitConsumer){
        //FruitService.getAllFruits();
        //some validations and get fruit object
        fruitConsumer.accept(fruit);
    }
}

//Some other class calling Util.acceptFruit(orange -> oranges.add(orange);

I get compilation errors on:

acceptFruit(ap -> appleList.add(ap);
add (Apple) in Collection can not be applied to (Fruit)

and on:

fruit.accept(fruit);
accept (T) in Consumer cannot be applied to (Apple)

Since Apple is extending Fruit, I don't understand why I am getting this error? Any idea on what I am missing in Generics/Consumer concepts?

Dadu
  • 373
  • 1
  • 3
  • 14
  • 1
    Read about [covariance and contravariance](https://dzone.com/articles/covariance-and-contravariance) (or [a longer treatise](https://medium.com/@sinisalouc/variance-in-java-and-scala-63af925d21dc)). – 9000 Apr 06 '18 at 20:36
  • `acceptFruit(ap -> appleList.add(ap);` doesn't compile, by the way. – Jacob G. Apr 06 '18 at 20:37
  • Modified my post, both are compilation errors. – Dadu Apr 06 '18 at 20:38
  • 1
    Hi @Radiodef, updated my question with correct syntax. Thanks – Dadu Apr 06 '18 at 20:53
  • HI @Aominè, So what can I do? I went over the covariance and contravariance link given above and seems like I can either read from the list using ` extends Fruit>` or write to a list using ` super Fruit>` but I can not do both? – Dadu Apr 06 '18 at 20:55
  • Maybe this reading will help - [PECS](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) - Producer Extend Consumer Super – takendarkk Apr 06 '18 at 20:58

3 Answers3

3

Apple may be a Fruit, but Collection<Apple> is not a Collection<Fruit>. This is due to the invariance of the generic type.

Ignoring the compilation error inside of acceptFruit (which to me is entirely unclear what you're doing to begin with), your best best is to ensure that your collection contains the superclass that you want to interface with instead of any subclasses.

 private Collection<Fruit> fruitList = new ArrayList<>();

This way, you can consume any fruit you wish in your consumer.

Anything more advanced than this - that is, consuming fruit of a specific kind - is more advanced than the basic generics we have here and is best left as an exercise for the reader.

Makoto
  • 104,088
  • 27
  • 192
  • 230
0

Apple is a subtype of Fruit, but Apple is not known to be a subtype of T, which is an unknown type. T could be Fruit, or Orange, or Banana, and in general Apple cannot be a subtype of all of them.

The way that your acceptFruit method is declared doesn't really make sense, because as a generic method, that means that it must just work no matter what T is. A caller of acceptFruit can call it with T being whatever the caller wants, and acceptFruit must work correctly without knowing what T is. So acceptFruit takes in an argument, a Consumer, but acceptFruit doesn't know at runtime what type that Consumer wants -- it could be a Consumer<Apple> one call, and a Consumer<Orange> the next call -- but acceptFruit has no way of inferring what type is wanted in a particular call, as it doesn't have any other arguments that tell it what it is. In order to invoke fruitConsumer it would need to pass in an instance of an unknown type. So the only ways that acceptFruit can be written safely are: 1) it never invokes fruitConsumer at all; or 2) it always passes null into fruitConsumer.

newacct
  • 119,665
  • 29
  • 163
  • 224
-1

Your acceptFruit() is weird because it calls fruit.accept(fruit). This code calls the consumer with itself as a parameter which causes all the mess in the generic. You have to pass instance of apple to the consumer, so here is your code without errors:

class Util {
    private Collection<Apple> appleList = new ArrayList<>();

    public Util(){
        acceptFruit(ap -> appleList.add(ap), new Apple());
    }

    public <T extends Fruit> void acceptFruit(Consumer<T> fruitConsumer, T fruit){
        //FruitService.getAllFruits();
        //some validations and get fruit object
        fruitConsumer.accept(fruit);
    }
}
bambula
  • 365
  • 1
  • 12
  • What happens if I want to put an `Orange` in there? – Makoto Apr 06 '18 at 21:02
  • You can't call `acceptFruit(ap -> appleList.add(ap), new Orange());` the compiler won't let you do that. You would need to use a consumer which consumes Oranges as first parameter to get it work. – bambula Apr 06 '18 at 21:05
  • How about this: **your collection is wrong.** You should be able to accept any type of fruit with a defined consumer, yet you *can't* because your collection is tied to `Apple`. – Makoto Apr 06 '18 at 21:10
  • I have no idea what was the point of such a code I just fixed it to get it work correctly. You can try it... Method `acceptFruit` can work with any consumer which works with Fruit and it's child classes. – bambula Apr 06 '18 at 21:14
  • I don't understand why you would want to pass in the type fruit along with consumer. Fruit, Apple or Orange objects will be created/retrieved from within the method `acceptFruit(..)` where I have the comments saying `//FruitService.getAllFruits(); //some validations and get fruit object` – Dadu Apr 06 '18 at 21:40