Limitations of forEach with instance method references in Java 8

Temp Agilist picture Temp Agilist · May 13, 2017 · Viewed 8k times · Source

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?

Answer

Andreas picture Andreas · May 13, 2017

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);