Ограничения 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);
Сообщение об ошибке говорит мне, что
-
forEach
вArraylist observers
не применимо к типуTemperatureObserver::react
-
TemperatureObserver
не определяет методreact(TemperatureObserver)
Достаточно справедливо, так как forEach
ожидает в качестве аргумента a Consumer<? super TemperatureObserver>
, и мой интерфейс, хотя и функциональный, не соответствует Consumer
из-за другого аргумента react
(a BigDecimal
в моем случае).
Так можно ли это решить, или это случай, в котором лямбда не имеет соответствующей ссылки на метод?
3 ответа:
Существует три вида ссылок на методы, которые можно использовать, когда одно значение доступно из потока:
Беспараметрический метод потокового объекта.
class Observer { public void act() { // code here } } observers.forEach(Observer::act); observers.forEach(obs -> obs.act()); // equivalent lambda
Потоковый объект становится объектом метода
this
.Статический метод с потоковым объектом в качестве параметра.
class Other { public static void act(Observer o) { // code here } } observers.forEach(Other::act); observers.forEach(obs -> Other.act(obs)); // equivalent lambda
Нестатический метод с потоковым объектом в качестве параметра.
class Other { void act(Observer o); } Other other = new Other(); observers.forEach(other::act); observers.forEach(obs -> other.act(obs)); // equivalent lambda
Существует также конструктор ссылка, но это не имеет отношения к данному вопросу.
Поскольку у вас есть внешнее значение
temp
, и вы хотите использовать ссылку на метод, вы можете сделать третий вариант: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);
Взгляните на раздел ссылок на методы в руководстве по 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));