web-dev-qa-db-fra.com

Limitations de forEach avec les références de méthode d'instance dans Java 8

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

  1. forEach dans le Arraylist observers ne s'applique pas au type TemperatureObserver::react
  2. 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?

20
Temp Agilist

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:

  1. 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.

  2. 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
    
  3. 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);
20
Andreas

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);
6
Bor Laze