web-dev-qa-db-fra.com

Référence de méthode Java8 utilisée comme objet Function pour combiner des fonctions

Existe-t-il un moyen en Java8 d'utiliser une référence de méthode en tant qu'objet Function pour utiliser ses méthodes, quelque chose comme:

Stream.of("ciao", "hola", "hello")
    .map(String::length.andThen(n -> n * 2))

Cette question n'est pas liée au Stream, elle est utilisée comme exemple, j'aimerais avoir une réponse sur la référence de la méthode

35
rascio

Vous pouvez écrire une méthode statique pour ce faire:

import Java.util.function.*;

class Test {
    public static void main(String[] args) {
        Function<String, Integer> function = combine(String::length, n -> n * 2);
        System.out.println(function.apply("foo"));
    }

    public static <T1, T2, T3> Function<T1, T3> combine(
        Function<T1, T2> first,
        Function<T2, T3> second) {
        return first.andThen(second);
    }
}

Vous pouvez ensuite le placer dans une classe utilitaire et l'importer statiquement.

Alternativement, créez une méthode statique plus simple qui juste retourne la fonction qui lui est donnée, pour que le compilateur sache ce que vous faites:

import Java.util.function.*;

class Test {
    public static void main(String[] args) {
        Function<String, Integer> function = asFunction(String::length).andThen(n -> n * 2);
        System.out.println(function.apply("foo"));
    }

    public static <T1, T2> Function<T1, T2> asFunction(Function<T1, T2> function) {
        return function;     
    }
}
31
Jon Skeet

Vous pouvez simplement l'enregistrer dans une variable:

Function<String, Integer> toLength = String::length;
Stream.of("ciao", "hola", "hello")
      .map(toLength.andThen(n -> n * 2));

Ou vous pouvez utiliser un casting, mais c'est moins lisible, IMO:

Stream.of("ciao", "hola", "hello")
      .map(((Function<String, Integer>) String::length).andThen(n -> n * 2));
22
JB Nizet

Vous devriez pouvoir réaliser ce que vous voulez en ligne en utilisant des transtypages:

Stream.of("ciao", "hola", "hello")
      .map(((Function<String, Integer>) String::length).andThen(n -> n * 2))

Il n'y a que des "indices de type" pour le compilateur, donc ils ne "castent" pas réellement l'objet et n'ont pas la surcharge d'un cast réel.


Alternativement, vous pouvez utiliser une variable locale pour la lisibilité:

Function<String, Integer> fun = String::length

Stream.of("ciao", "hola", "hello")
      .map(fun.andThen(n -> n * 2));

Une troisième façon qui peut être plus concise est d'utiliser une méthode utilitaire:

public static <T, X, U> Function<T, U> chain(Function<T, X> fun1, Function<X, U> fun2)
{
    return fun1.andThen(fun2);
}

Stream.of("ciao", "hola", "hello")
      .map(chain(String::length, n -> n * 2));

Veuillez noter que ceci n'est pas testé, donc je ne sais pas si l'inférence de type fonctionne correctement dans ce cas.

11
Clashsoft

Vous pouvez également utiliser

Function.identity().andThen(String::length).andThen(n -> n * 2)

Le problème est, String::length n'est pas nécessairement un Function; il peut se conformer à de nombreuses interfaces fonctionnelles. Il doit être utilisé dans un contexte qui fournit le type cible, et le contexte peut être - affectation, invocation de méthode, transtypage.

Si Function pouvait fournir une méthode statique uniquement pour le typage cible, nous pourrions faire

    Function.by(String::length).andThen(n->n*2)

static <T, R> Function<T, R> by(Function<T, R> f){ return f; }

Par exemple, j'utilise cette technique dans ne interface fonctionnelle

static <T> AsyncIterator<T> by(AsyncIterator<T> asyncIterator)

Sucre de syntaxe pour créer un AsyncIterator à partir d'une expression lambda ou d'une référence de méthode.

Cette méthode renvoie simplement l'argument asyncIterator, ce qui semble un peu étrange. Explication:

AsyncIterator étant une interface fonctionnelle, une instance peut être créée par une expression lambda ou une référence de méthode, dans 3 contextes:

 // Assignment Context
 AsyncIterator<ByteBuffer> asyncIter = source::read;
 asyncIter.forEach(...);

 // Casting Context
 ((AsyncIterator<ByteBuffer>)source::read)
     .forEach(...);

 // Invocation Context
 AsyncIterator.by(source::read)
     .forEach(...);

La 3ème option est meilleure que les deux autres, et c'est le but de cette méthode.

7
ZhongYu

Vous pouvez utiliser un casting

Stream.of("ciao", "hola", "hello")
        .map(((Function<String, Integer>) String::length)
                .andThen(n -> n * 2))
        .forEach(System.out::println);

impressions

8
8
10
5
Peter Lawrey

Vous pourriez écrire:

Stream.of("ciao", "hola", "hello").map(String::length).map(n -> n * 2);
4
Tunaki

Nous pouvons utiliser la fonction reduce pour combiner plusieurs fonctions en une seule.

public static void main(String[] args) {
    List<Function<String, String>> normalizers = Arrays.asList(
    str -> {
        System.out.println(str);
        return str;
    }, 
    String::toLowerCase,
    str -> {
        System.out.println(str);
        return str;
    },
    String::trim,
    str -> {
        System.out.println(str);
        return str;
    });

    String input = " Hello World ";
    normalizers.stream()
               .reduce(Function.identity(), (a, b) -> a.andThen(b))
               .apply(input);
}

Production:

 Hello World 
 hello world 
hello world
0
Kanagavelu Sugumar