22

Assume I have the following functional interface:

public interface TemperatureObserver {
    void react(BigDecimal t);
}

and then in another class an already filled-in ArrayList of objects of type TemperatureObserver. Assuming that temp is a BigDecimal, I can invoke react in a loop using:

observers.forEach(item -> item.react(temp));

My question: can I use a method reference for the code above?

The following does not work:

observers.forEach(TemperatureObserver::react);

The error message is telling me that

  1. forEach in the Arraylist observers is not applicable to the type TemperatureObserver::react
  2. TemperatureObserver does not define a method react(TemperatureObserver)

Fair enough, as forEach expects as an argument a Consumer<? super TemperatureObserver>, and my interface, although functional, does not comply to Consumer because of the different argument of react (a BigDecimal in my case).

So can this be solved, or it is a case in which a lambda does not have a corresponding method reference?

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
Temp Agilist
  • 233
  • 1
  • 2
  • 8
  • The method you're referencing is in an interface. There's no implementation, so it doesn't even make sense to use here. If you'd make it a class and use an instance of that class for the method reference(e.g. `instanceTempObs::react`) or make the method static, it would work.(IF your list is typed to `BigDecimal`) – Riiverside May 13 '17 at 20:37
  • 5
    It would be possible if java.lang.Iterable had a second forEach method which took a BiConsumer and a parameter. Then you could use your method reference and pass the BigDecimal as the second parameter. This pattern exists in Eclipse Collections in methods that have a suffix of "With" (e.g. forEachWith). https://github.com/eclipse/eclipse-collections/blob/master/eclipse-collections-api/src/main/java/org/eclipse/collections/api/InternalIterable.java#L106 – Donald Raab May 14 '17 at 03:00
  • 1
    See [currying](https://en.wikipedia.org/wiki/Currying). – Boris the Spider May 14 '17 at 07:33

3 Answers3

24

There are three kinds of method references that can be used when a single value is available from the stream:

  1. A parameter-less method of the streamed object.

    class Observer {
        public void act() {
            // code here
        }
    }
    
    observers.forEach(Observer::act);
    
    observers.forEach(obs -> obs.act()); // equivalent lambda
    

    The streamed object becomes the this object of the method.

  2. A static method with the streamed object as parameter.

    class Other {
        public static void act(Observer o) {
            // code here
        }
    }
    
    observers.forEach(Other::act);
    
    observers.forEach(obs -> Other.act(obs)); // equivalent lambda
    
  3. A non-static method with the streamed object as parameter.

    class Other {
        void act(Observer o);
    }
    
    Other other = new Other();
    observers.forEach(other::act);
    
    observers.forEach(obs -> other.act(obs)); // equivalent lambda
    

There is also a constructor reference, but that is not really relevant to this question.

Since you have an external value temp, and you want to use a method reference, you can do the third option:

class Temp {
    private final BigDecimal temp;
    public Temp(BigDecimal temp) {
        this.temp = temp;
    }
    public void apply(TemperatureObserver observer) {
        observer.react(this.temp);
    }
}

Temp tempObj = new Temp(temp);

observers.forEach(tempObj::apply);
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 7
    @TempAgilist while Andreas is right here, I would doubt that using a class to wrap a value, *just to use a method reference* would add any value; besides being hard to understand by future maintainers. – Eugene May 14 '17 at 08:24
  • 2
    @Eugene: agreed. Your remark stands also for the valid solution by Federico (see below) using a helper method. I was just wondering if I was missing a simple solution using method references. Now I know I did not :) – Temp Agilist May 14 '17 at 09:22
  • 1
    @TempAgilist Aahh... How I wish I could do `TemperatureObserver::react(temp)` – fps May 14 '17 at 15:33
  • @Federico Peralta Schaffner: Why? What’s the advantage of `TemperatureObserver::react(temp)` over `item -> item.react(temp)`? – Holger May 15 '17 at 16:35
  • @Holger With the lambda you don't know the type of `item`, while with method references you do. Another one could be readability. But if you insist, I would respond that main advantage is that I'd like it so much... I mean, if the language offers different ways to do the same thing, I think that it is better. Actually, that happens with all available method references. Maybe we should ask ourselves what's their advantage at all, because all method references can be translated to a lambda. – fps May 15 '17 at 16:38
  • When you write `observers.forEach(item -> item.react(temp));` you roughly get what this solution does, except that you don’t have to write the `Temp` class or instantiation, instead, it will generated at runtime when needed. – Holger May 15 '17 at 16:38
  • @Holger But same argument holds for `item -> System.out.println(item)` vs `System.out::println`. So, correct me if I'm wrong, but I think that method references are like they are now just because a matter of implementation, i.e. they are not allowed to capture final/effectively final variables. Is this correct? – fps May 15 '17 at 16:43
  • @Federico Peralta Schaffner: you can write `(TemperatureObserver item) -> item.react(temp)` if you want to denote the type. But it seems, a lot of developers have an irrational wish for solving everything with method references. They are nice and concise if there’s a precisely matching method, but there is no advantage over lambda expressions, when we have to specify additional parameters or transformations. That’s exactly what lambda expressions serve. – Holger May 15 '17 at 16:45
  • @Holger Well, if you could write `TemperatureObserver::react(temp)` you would have an already matching method. I'm already saying that this is a matter of taste. In my answer I'm even using the typed lambda... It's just that I like method references so much... call it irrational if you want. I also believe that it would improve readability, but again, this is soooo debatable... Anyways, I know that lambdas already serve this purpose. – fps May 15 '17 at 16:53
  • @Federico Peralta Schaffner: Method references could capture an arbitrary number of parameter values from left to right, not just the first one like with `expression::method`, that’s exactly how lambda expressions are implemented. The design choice is a syntactical one. You need a syntax for specifying the intended parameters and that’s what lambda expressions are for. Some of them could be compiled like method references, i.e. without the synthetic method, if their captured values have the left-to-right form. The `arg -> arg.method(captured)` does not have that form. – Holger May 15 '17 at 16:55
  • @Holger Understood, thanks for your arguments in this bizantine, academic, mini-discussion :) – fps May 15 '17 at 16:57
9

Take a look at the Method References section in the Java Tutorial. There it says:

There are four kinds of method references:

  • Reference to a static method: ContainingClass::staticMethodName

  • Reference to an instance method of a particular object: containingObject::instanceMethodName

  • Reference to an instance method of an arbitrary object of a particular type: ContainingType::methodName

  • Reference to a constructor: ClassName::new

There it explains that i.e. TemperatureObserver::react would be a method reference of the 3rd type: a reference to an instance method of an arbitrary object of a particular type. In the context of your call to the Stream.forEach method, that method reference would be equivalent to the following lambda expression:

(TemperatureObserver item) -> item.react()

Or just:

item -> item.react()

Which doesn't match your void TemperatureObserver.react(BigDecimal t) method signature.

As you already suspect, there are cases for which you can't find an equivalent method reference for a lambda. Lambdas are way more flexible, though IMHO sometimes they are less readable than method references (but this is a matter of taste, many people think the other way round).

A way to still use a method reference would be with a helper method:

public static <T, U> Consumer<? super T> consumingParam(
        BiConsumer<? super T, ? super U> biConsumer,
        U param) {

    return t -> biConsumer.accept(t, param);
}

Which you could use as follows:

observers.forEach(consumingParam(TemperatureObserver::react, temp));

But, honestly, I prefer to use a lambda.

Community
  • 1
  • 1
fps
  • 33,623
  • 8
  • 55
  • 110
6

It does not works, because you iterate over handlers, not over parameters.

For example, this code works:

    ArrayList<BigDecimal> temps = new ArrayList<>();

    TemperatureObserver observer = new TemperatureObserverImpl();

    temps.forEach(observer::react);
Bor Laze
  • 2,458
  • 12
  • 20
  • Assuming `TemperatureObserver` is a class rather than an interface. Alternatively, you could have something like `TemperatureObserverImpl` which implements the interface. – Code-Apprentice May 13 '17 at 20:45