2

In my situation, I have a consumer that takes a supplier of ? extends String and executes some action on it, so the declaration goes like this :

final Consumer<? super Supplier<? extends String>> action = ...

The problem is when I try to execute the action, the compiler doesn't seem to be happy and and blows the following error :

The target type of this expression must be a functional interface

In my case, I have a customer with a name so the following line triggers this error :

action.accept(customer::getName)

My assumption is that ? super Supplier<? extends String> is not considered anymore a functional interface as it is a capture type.

So, can someone give me a clear explanation on this situation ?

marsouf
  • 1,107
  • 8
  • 15
  • Please tell us which version of the JDK you are using and also provide the whole error message. – fps Apr 20 '18 at 12:49
  • 1
    Not sure if it's the actual cause of your problem, but just because `Supplier` is a `@FunctionalInterface` does *not* mean that any super class/interface of it is also a `@FunctionalInterface`. So `? super Supplier` can be erased to `Object` which is not a `@FunctionalInterface`. You probably don't mean `? super Supplier` but just `Supplier` here. – JimmyB Apr 20 '18 at 13:48

2 Answers2

3

Why are you using this token, ? super Supplier. What is the purpose of that? You have to pass just a Supplier which is declared as a @FunctionalInterface. Try this out instead,

public class MyConsumer implements Consumer<Supplier<String>> {
    @Override
    public void accept(Supplier<String> t) {
        System.out.println(t.get());

    }

    public static void main(String[] args) {
        MyConsumer myConsumer = new MyConsumer();
        myConsumer.accept(() -> "Test");
    }
}

@FunctionalInterface annotation is useful for compilation time checking of your code. You cannot have more than one method there. For an instance Runnable, Callable are good candidates for this.

However you can further condense it down like so:

Consumer<Supplier<String>> myConsumer = (Supplier<String> supplier) -> System.out.println(supplier.get());
myConsumer.accept(() -> "Test");

Since the compiler can infer the types, this would further be simplified as,

Consumer<Supplier<String>> myConsumer = supplier -> System.out.println(supplier.get());
myConsumer.accept(() -> "Test");
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
1

That’s simply a flaw in the type inference. Note that while

final Consumer<? super Supplier<? extends String>> action=s -> System.out.println(s.get());
action.accept(() -> "foo");

fails with a compiler error

final Consumer<? super Supplier<? extends String>> action=s -> System.out.println(s.get());
action.accept((Supplier<? extends String>)() -> "foo");

compiles and runs without problems. And so does

final Consumer<? super Supplier<? extends String>> action=s -> System.out.println(s.get());
final Supplier<? extends String> supp = () -> "foo";
action.accept(supp);

So the compiler simply didn’t infer the target type Supplier<? extends String> for the parameter type ? super Supplier<? extends String>. Note that on the other hand, it does infer Supplier<String> as target type for the assignment to Supplier<? extends String> when providing a lambda expression.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • So maybe this should be reported as a bug? – fps Apr 20 '18 at 12:47
  • @FedericoPeraltaSchaffner I don’t know whether the behavior matches the specification. I just used the word “flaw” from the practical point of view. I didn’t have the time to check the specification yet and this part of the spec is one of toughest… – Holger Apr 20 '18 at 13:41
  • 1
    I don't think it's a "flaw": `Consumer super Supplier>` would even match `Consumer`, and `Object` is not a funtional interface. By downcasting to `Supplier` you make the potentially-Object an instance of a functional interface again. – JimmyB Apr 20 '18 at 13:56
  • 2
    @JimmyB `Consumer super Supplier…>` *could* be a `Consumer`, but you see at the `action` variable that the compiler does not assume `Consumer`, as the lambda expression `s -> System.out.println(s.get())` invokes the supplier’s `get` method without problems. Here, the compiler *does* assume the most specific type which is `Consumer>`. And it could do the same for the invocation of `accept`. See the last example of my answer which doesn’t have a type cast. These assignments are valid. – Holger Apr 20 '18 at 14:03
  • 1
    @Holger No, your last example does not have an explicit cast. But your `supp` is *not* of type `? super Supplier` but of type `Supplier` which amounts to the same as the other example with an explicit cast. When *using* `action` its *declared* type is what matters to the compiler, and that type does not guarantee that there's a supplier involved. In other words, `? super Supplier` means that `consumer.accept(new Object())` would be valid w.r.t. generic types. Hence, the compiler only knows that some *object* is required and fails to see how `customer::getName` should target the non – JimmyB Apr 20 '18 at 14:19
  • functional interface type `Object`. – JimmyB Apr 20 '18 at 14:19
  • By the way, `? super X` is meant to designate some kind of 'container' for objects of type `X`, i.e. it is guaranteed that I can assign an instance of type `X` to a given variable (ensures assignability). In the case of the OP this is not needed because he won't be assigning anything to the given supplier reference inside his consumer. – JimmyB Apr 20 '18 at 14:23
  • 1
    @JimmyB You can **not** do `consumer.accept(new Object())`. The declaration `Consumer super Supplier extends String>> action` implies that you can assign a `Consumer` to `action`, because a `Consumer` can also handle `Supplier extends String>` arguments in its `accept` method. But since it is also possible to assign a `Consumer>` to it, as happening implicitly in my examples, it would never be valid to pass `new Object()` to the `accept` method. The argument to `accept` *must* be assignable to `Supplier extends String>`. So why not infer that… – Holger Apr 20 '18 at 15:01
  • @Holger No, the argument to accept must be a *super* type of Supplier. That's what ? super Supplier declares, and that does not make sense in this case. Consumer declares what you intend, making only instances of Supplier acceptable. – JimmyB Apr 21 '18 at 17:17
  • @JimmyB: Apparently, you are so convinced of yourself that you didn’t even *try* it. Since you are only repeating yourself, this pointless discussion has to stop here. – Holger Apr 23 '18 at 06:33
  • @Holger While I do understand your point, obviously the compiler proves both of us wrong: It does not in fact accept `new Object()` (**although** the effective declared method becomes `accept(Object t)`), but on the other hand it does not infer the need for a `Supplier` (**because** the effective declared method becomes `accept(Object t)`). – JimmyB Apr 24 '18 at 10:45
  • @JimmyB: There is no such thing as “effective declared method”. Apparently you are confusing this with type erasure, but type erasure has nothing to do with what is valid under the generic type system. As said several times, the signature `Consumer super Type>` implies that the `accept` method requires `Type`, the relevant difference to `Consumer` is that the actual implementation may be a consumer of a super type. But in the example of the answer, it is not. There, it is a `Consumer>`. – Holger Apr 24 '18 at 15:03
  • @Holger Yes, I am referring to type erasure. And through that, `Consumer super X>` gets its `accept()` as `accept( super X>)` (itself not valid syntax), and that method should accept anything of `X` or a super type, hence when typing eclipse offers me `accept(Object t)`. – JimmyB Apr 24 '18 at 15:33
  • @Holger I think, together we can pin down the issue: You are correct in that `Consumer super X> action` says something about *which* kinds of consumers may be assigned to `action`; could be `Constumer` if one wants. Now, *if* I assigned a `Constumer` to `action` at some point in my program, `action.accept(...)` would accept `Object` which is not a functional interface. Thus, from the *declared* type of `action` the compiler cannot infer that the actual instance is restricted to functional interfaces, because it might not. – JimmyB Apr 24 '18 at 15:37
  • Btw, I just tried once more and my eclipse Neon actually only suggests `accept(Supplier<...> t)` while Oxygen on my laptop showed `accept(Object t)`. – JimmyB Apr 24 '18 at 15:43
  • @JimmyB there is no need for the target type to be “restricted to functional interfaces”. See, whenever there is a variable or parameter of type `X`, `X` being a functional interface, you could also assign `Y`, a subtype of `X` not being a functional interface. The variable is never “restricted to functional interfaces”. And once you have assigned the lambda expression to a variable or parameter of a functional interface type, the receiving code could always assign that to a wider type like `Object`. All that matters is whether the compiler understands what to do before assigning the result. – Holger Apr 24 '18 at 16:21
  • @JimmyB Regarding type erasure, when you write, e.g. `Set l = Collections.singleton(() -> System.out.println("hello"));`, you are also passing the lambda expression to a method receiving `Object` under the cover, as `singleton` accepts arbitrary types, not even remotely restricted to lambda expression. It’s only our generic (compile-time) usage, i.e. assigning to a `Set` (which will be just `Set` after erasure), which determines the lambda’s target type for the compiler. – Holger Apr 24 '18 at 16:23
  • @Holger Not sure I get your point. If `X` is a functional interface, then `Y` extending `X` should implicitly be a functional interface too. So whatever I can do on a functional interface `X`, I can do on `Y` too. – JimmyB Apr 24 '18 at 18:20
  • `Supplier s = obj::toString;` works, `Object o = obj::toString;` doesn't. – JimmyB Apr 24 '18 at 18:23