Ограничения forEach со ссылками на метод экземпляра в Java 8

предположим, что у меня есть следующий функциональный интерфейс:

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

и затем в другом классе уже заполненный ArrayList объектов типа TemperatureObserver. Предполагая, что temp это BigDecimal, Я могу вызвать react в цикле, используя:

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

у меня вопрос: могу ли я использовать ссылку на метод для кода выше?

следующее не работает:

observers.forEach(TemperatureObserver::react);

сообщение об ошибке говорит мне, что

  1. forEach на Arraylist observers не применяется к типу TemperatureObserver::react
  2. TemperatureObserver не определяет способ react(TemperatureObserver)

справедливо, как forEach ожидает в качестве аргумента Consumer<? super TemperatureObserver>, и мой интерфейс, хотя и функциональный, не соответствует Consumer из-за другого аргумента react (a BigDecimal в моем случае).

таким образом, это может быть решена, или это случай, в котором лямбда не имеет соответствующего ссылка на метод?

3 ответов


существует три вида ссылок на методы, которые можно использовать, когда одно значение доступно из потока:

  1. метод без параметров потокового объекта.

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

    потоковый объект становится


посмотри раздел ссылок на методы в учебнике Java. Там написано:

существует четыре вида ссылок на метод:

  • ссылка на статический метод: ContainingClass::staticMethodName

  • ссылка на метод экземпляра конкретного объекта: containingObject::instanceMethodName

  • ссылка на метод экземпляра произвольного объекта определенного типа: ContainingType::methodName

  • ссылка на конструктор: ClassName::new

там он объясняет, что т. е. TemperatureObserver::react будет эталонным методом 3-го типа: ссылка на метод экземпляра произвольного объекта определенного типа. В контексте вашего призыва к Stream.forEach метод, эта ссылка на метод будет эквивалентна следующему лямбда-выражению:

(TemperatureObserver item) -> item.react()

или так:

item -> item.react()

что не подходит void TemperatureObserver.react(BigDecimal t) сигнатуру метода.

как вы уже подозреваете, есть случаи, для которых вы не можете найти эквивалентную ссылку метода для лямбды. Лямбды более гибкие, хотя ИМХО иногда они менее читабельны, чем ссылки на методы (но это вопрос вкуса, многие люди думают наоборот).

способ по-прежнему использовать ссылку на метод будет с вспомогательным методом:

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

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

который вы могли бы использовать как следует:

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

но, честно говоря, я предпочитаю использовать лямбда.


это не работает, потому что вы перебираете обработчики, а не параметры.

например, этот код работает:

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

    TemperatureObserver observer = new TemperatureObserverImpl();

    temps.forEach(observer::react);