Supposons que j'ai l'interface fonctionnelle suivante:
public interface TemperatureObserver {
void react(BigDecimal t);
}
puis dans une autre classe un ArrayList
déjà rempli d'objets de type TemperatureObserver
. En supposant que temp
est un BigDecimal
, je peux invoquer react
dans une boucle en utilisant:
observers.forEach(item -> item.react(temp));
Ma question: puis-je utiliser une référence de méthode pour le code ci-dessus?
Ce qui suit ne fonctionne pas:
observers.forEach(TemperatureObserver::react);
Le message d'erreur me dit que
forEach
dans le Arraylist observers
ne s'applique pas au type TemperatureObserver::react
TemperatureObserver
ne définit pas de méthode react(TemperatureObserver)
Assez juste, comme forEach
attend comme argument un Consumer<? super TemperatureObserver>
, Et mon interface, bien que fonctionnelle, ne se conforme pas à Consumer
à cause de l'argument différent de react
(un BigDecimal
dans mon cas).
Cela peut-il donc être résolu, ou s'agit-il d'un cas dans lequel un lambda n'a pas de référence de méthode correspondante?
Il existe trois types de références de méthode qui peuvent être utilisées lorsqu'une seule valeur est disponible dans le flux:
Une méthode sans paramètre de l'objet diffusé.
class Observer {
public void act() {
// code here
}
}
observers.forEach(Observer::act);
observers.forEach(obs -> obs.act()); // equivalent lambda
L'objet diffusé devient l'objet this
de la méthode.
Une méthode statique avec l'objet streamé comme paramètre.
class Other {
public static void act(Observer o) {
// code here
}
}
observers.forEach(Other::act);
observers.forEach(obs -> Other.act(obs)); // equivalent lambda
Une méthode non statique avec l'objet diffusé comme paramètre.
class Other {
void act(Observer o);
}
Other other = new Other();
observers.forEach(other::act);
observers.forEach(obs -> other.act(obs)); // equivalent lambda
Il existe également une référence de constructeur, mais ce n'est pas vraiment pertinent pour cette question.
Puisque vous avez une valeur externe temp
et que vous souhaitez utiliser une référence de méthode, vous pouvez faire la troisième 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);
Jetez un oeil à la section Références de méthode dans le Java . Là, il dit:
Il existe quatre types de références de méthode:
Référence à une méthode statique:
ContainingClass::staticMethodName
Référence à une méthode d'instance d'un objet particulier:
containingObject::instanceMethodName
Référence à une méthode d'instance d'un objet arbitraire d'un type particulier:
ContainingType::methodName
Référence à un constructeur:
ClassName::new
Il explique que c'est-à-dire que TemperatureObserver::react
Serait une référence de méthode du 3ème type: une référence à une méthode d'instance d'un objet arbitraire d'un type particulier. Dans le contexte de votre appel à la méthode Stream.forEach
, Cette référence de méthode serait équivalente à l'expression lambda suivante:
(TemperatureObserver item) -> item.react()
Ou juste:
item -> item.react()
Ce qui ne correspond pas à votre signature de méthode void TemperatureObserver.react(BigDecimal t)
.
Comme vous le soupçonnez déjà, il existe des cas pour lesquels vous ne pouvez pas trouver une référence de méthode équivalente pour un lambda. Les lambdas sont beaucoup plus flexibles, même si à mon humble avis, ils sont parfois moins lisibles que les références de méthode (mais c'est une question de goût, beaucoup de gens pensent le contraire).
Une façon d'utiliser toujours une référence de méthode serait d'utiliser une méthode d'assistance:
public static <T, U> Consumer<? super T> consumingParam(
BiConsumer<? super T, ? super U> biConsumer,
U param) {
return t -> biConsumer.accept(t, param);
}
Que vous pourriez utiliser comme suit:
observers.forEach(consumingParam(TemperatureObserver::react, temp));
Mais, honnêtement, je préfère utiliser un lambda.
Cela ne fonctionne pas, car vous parcourez les gestionnaires, pas les paramètres.
Par exemple, ce code fonctionne:
ArrayList<BigDecimal> temps = new ArrayList<>();
TemperatureObserver observer = new TemperatureObserverImpl();
temps.forEach(observer::react);