métodos - Limitaciones de forEach con referencias de método de instancia en Java 8
metodo static java (3)
Eche un vistazo a la sección Referencias de métodos en el Tutorial de Java . Allí dice:
Hay cuatro tipos de referencias de métodos:
Referencia a un método estático:
ContainingClass::staticMethodName
Referencia a un método de instancia de un objeto particular:
containingObject::instanceMethodName
Referencia a un método de instancia de un objeto arbitrario de un tipo particular:
ContainingType::methodName
Referencia a un constructor:
ClassName::new
Allí se explica que, por ejemplo, TemperatureObserver::react
sería una referencia de método del tercer tipo: una referencia a un método de instancia de un objeto arbitrario de un tipo particular. En el contexto de su llamada al método Stream.forEach
, esa referencia de método sería equivalente a la siguiente expresión lambda:
(TemperatureObserver item) -> item.react()
O solo:
item -> item.react()
Que no coincide con la firma del método void TemperatureObserver.react(BigDecimal t)
.
Como ya sospecha, hay casos en los que no puede encontrar una referencia de método equivalente para un lambda. Las Lambdas son mucho más flexibles, aunque en mi humilde opinión a veces son menos legibles que las referencias a métodos (pero esto es una cuestión de gustos, muchas personas piensan al revés).
Una forma de seguir usando una referencia de método sería con un método auxiliar:
public static <T, U> Consumer<? super T> consumingParam(
BiConsumer<? super T, ? super U> biConsumer,
U param) {
return t -> biConsumer.accept(t, param);
}
Que puedes usar de la siguiente manera:
observers.forEach(consumingParam(TemperatureObserver::react, temp));
Pero, honestamente, prefiero usar un lambda.
Supongamos que tengo la siguiente interfaz funcional:
public interface TemperatureObserver {
void react(BigDecimal t);
}
y luego, en otra clase, una lista de ArrayList
ya completada de objetos de tipo TemperatureObserver
. Suponiendo que temp
es un BigDecimal
, puedo invocar react
en un bucle usando:
observers.forEach(item -> item.react(temp));
Mi pregunta : ¿Puedo usar una referencia de método para el código anterior?
Lo siguiente no funciona:
observers.forEach(TemperatureObserver::react);
El mensaje de error me dice que
-
forEach
en losArraylist observers
no es aplicable al tipoTemperatureObserver::react
-
TemperatureObserver
no define un método dereact(TemperatureObserver)
Justo lo suficiente, como forEach
espera como argumento un Consumer<? super TemperatureObserver>
Consumer<? super TemperatureObserver>
, y mi interfaz, aunque funcional, no cumple con el Consumer
debido a los diferentes argumentos de react
(un BigDecimal
en mi caso).
Entonces, ¿se puede resolver esto o es un caso en el que un lambda no tiene una referencia de método correspondiente?
Hay tres tipos de referencias de métodos que se pueden usar cuando un solo valor está disponible en el flujo:
Un método sin parámetros del objeto transmitido.
class Observer { public void act() { // code here } } observers.forEach(Observer::act); observers.forEach(obs -> obs.act()); // equivalent lambda
El objeto transmitido se convierte en el objeto de este método.
Un método estático con el objeto transmitido como parámetro.
class Other { public static void act(Observer o) { // code here } } observers.forEach(Other::act); observers.forEach(obs -> Other.act(obs)); // equivalent lambda
Un método no estático con el objeto transmitido como parámetro.
class Other { void act(Observer o); } Other other = new Other(); observers.forEach(other::act); observers.forEach(obs -> other.act(obs)); // equivalent lambda
También hay una referencia del constructor, pero eso no es realmente relevante para esta pregunta.
Como tiene una temp
valor externa y desea usar una referencia de método, puede hacer la tercera opción:
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);
No funciona, porque se itera sobre los manejadores, no sobre los parámetros.
Por ejemplo, este código funciona:
ArrayList<BigDecimal> temps = new ArrayList<>();
TemperatureObserver observer = new TemperatureObserverImpl();
temps.forEach(observer::react);