Question

Quelle est la manière préférée de réduire une liste d'UnaryOperators dans Java 8 jusqu'à ce qu'ils représentent un UnaryOperator sur lequel je peux appeler ?Par exemple j'ai ce qui suit

interface MyFilter extends UnaryOperator<MyObject>{};

public MyObject filterIt(List<MyFilter> filters,MyObject obj){
Optional<MyFilter> mf = filters
                           .stream()
                           .reduce( (f1,f2)->(MyFilter)f1.andThen(f2));

return mf.map(f->f.apply(obj)).orElse(obj);

}

Mais ce code lance un ClassCastException à (MyFilter)f1.andThen(f2).Je veux vraiment l'effet de ce code au final :

MyObject o = obj;
for(MyFilter f:filters){
  o = f.apply(o);
}
return o;

Mais je suis également curieux de savoir comment réduire un ensemble de fonctions à une seule fonction, en utilisant compose ou andThen.

Était-ce utile?

La solution

Le problème de l'utilisation compose ou andThen c'est qu'ils sont intégrés au Function interface et le type -- à la fois au moment de la compilation et au moment de l'exécution -- des fonctions qu'ils renvoient est Function et pas UnaryOperator ou une sous-interface telle que vous l'avez définie.Par exemple, supposons que nous ayons

UnaryOperator<String> a = s -> s + "bar";
UnaryOperator<String> b = s -> s + s;

On pourrait penser qu'on pourrait écrire

UnaryOperator<String> c = a.compose(b);

mais ça ne marche pas !Il faut plutôt écrire

Function<String, String> c = a.compose(b);

Pour que cela fonctionne, UnaryOperator devrait fournir des remplacements de covariants de andThen et compose.(Il s'agit sans doute d'un bug dans l'API.) Vous feriez la même chose dans votre sous-interface.Ou bien, c'est assez simple d'écrire les lambdas à la main.Par exemple,

interface MyOperator extends UnaryOperator<String> { }

public static void main(String[] args) {
    List<MyOperator> list =
        Arrays.asList(s -> s + "bar",
                      s -> "[" + s + "]",
                      s -> s + s);

    MyOperator composite =
        list.stream()
            .reduce(s -> s, (a, b) -> s -> b.apply(a.apply(s)));

    System.out.println(composite.apply("foo"));
}

Ceci s'imprime [foobar][foobar].Notez que j'ai utilisé la forme à deux arguments de reduce afin d'éviter d'avoir à gérer Optional.

Alternativement, si vous faites beaucoup de composition de fonctions, vous pouvez réimplémenter les méthodes dont vous avez besoin dans votre propre interface.Ce n'est pas trop difficile.Ceux-ci sont basés sur les implémentations dans java.util.Function mais avec le béton String le type que j'ai utilisé dans cet exemple en remplacement des génériques.

interface MyOperator extends UnaryOperator<String> {
    static MyOperator identity() {
        return s -> s;
    }

    default MyOperator andThen(MyOperator after) {
        Objects.requireNonNull(after);
        return s -> after.apply(this.apply(s));
    }

    default MyOperator compose(MyOperator before) {
        Objects.requireNonNull(before);
        return s -> this.apply(before.apply(s));
    }
}

Celui-ci serait utilisé comme suit :

MyOperator composite =
    list.stream()
        .reduce(MyOperator.identity(), (a, b) -> a.andThen(b));

Qu'il s'agisse de gonfler l'interface pour écrire andThen au lieu d'un lambda imbriqué, c'est une question de goût, je suppose.

Autres conseils

MyFilter hérite de la méthode andThen depuis Function et donc le type renvoyé est Function et ne peut pas être converti en MyFilter.Mais comme il possède la signature de fonction souhaitée, vous pouvez créer le MyFilter instance en utilisant un lambda ou une référence de méthode.

Par exemple.changement (f1,f2)->(MyFilter)f1.andThen(f2) à (f1,f2)-> f1.andThen(f2)::apply.

Avec ce changement, la méthode entière ressemble à :

public static MyObject filterIt(List<MyFilter> filters, MyObject obj) {
    Optional<MyFilter> mf =
      filters.stream().reduce( (f1,f2)-> f1.andThen(f2)::apply);
    return mf.map(f->f.apply(obj)).orElse(obj);
}

Mais vous devriez repenser votre conception.Il n'est pas nécessaire que la fonction résultante soit une instance de MyFilter, en fait, même la saisie peut être assouplie pour accepter plus que simplement List<MyFilter>:

// will accept List<MyFilter> as input
public static MyObject filterIt(
 List<? extends Function<MyObject,MyObject>> filters, MyObject obj) {
   List<Function<MyObject,MyObject>> l=Collections.unmodifiableList(filters);
   Optional<Function<MyObject,MyObject>> mf=l.stream().reduce(Function::andThen);
   return mf.orElse(Function.identity()).apply(obj);
}

ou, en utilisant l'astuce de Stuart Marks pour se débarrasser du Optional:

// will accept List<MyFilter> as input
public static MyObject filterIt(
  List<? extends Function<MyObject,MyObject>> filters,MyObject obj) {
    List<Function<MyObject,MyObject>> l=Collections.unmodifiableList(filters);
    return l.stream().reduce(Function.identity(), Function::andThen).apply(obj);
}

Juste pour être complet, vous pouvez également enchaîner vos MyFilters sur un flux plutôt que de composer une nouvelle fonction :

public static MyObject filterIt2(List<MyFilter> filters,MyObject obj) {
    Stream<MyObject> s=Stream.of(obj);
    for(MyFilter f: filters) s=s.map(f);
    return s.findAny().get();
}

Une réponse courte :N'utilisez jamais l'interface UnaryOperator<T>, utiliser Function<T, T> plutôt.Si vous faites cela, les méthodes composent et andThen fonctionnera comme prévu.

Il est possible de convertir une interface fonctionnelle en une autre interface fonctionnelle en utilisant une syntaxe de référence de méthode sur sa méthode abstraite.

import java.util.function.UnaryOperator;
import java.util.stream.Stream;

public class Example {
    public static void main(String[] args) {
        Stream<UnaryOperator<String>> stringMappers = Stream.of(
                s -> s + "bar",
                s -> "[" + s + "]",
                s -> s + s
        );
        UnaryOperator<String> f = stringMappers.reduce(
                s -> s,
                (a, b) -> a.andThen(b)::apply
        );
        System.out.println(f.apply("foo"));
    }
}

Vous pouvez faire un casting pour Function<?, ?> avant de faire la réduction :

interface MyFilter extends UnaryOperator<MyObject>{};

Function<MyObject, MyObject> mf = filters.stream()
    .map(f -> (Function<MyObject, MyObject>) f)
    .reduce(Function.identity(), Function::compose);

Cela vous permet d'utiliser le Function.identity() et Function::compose méthodes, évitant ainsi d'avoir à les réimplémenter dans votre interface (comme l'a suggéré Stuart Marks).

Le plâtre est toujours en sécurité car il s'élève/s'élargit.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top