2015-08-25 1 views
30

Y at-il une façon Java8 d'utiliser une référence de méthode comme un objet Function d'utiliser ses méthodes, quelque chose comme:référence de la méthode Java8 utilisé comme objet de fonction pour combiner les fonctions

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

Cette question n'est pas liée à la Stream, il est utilisé comme exemple, je voudrais avoir réponse au sujet de la référence de la méthode

+0

Indiquez l'entrée et la sortie désirée, puis expliquez comment implémenter des flux Java 8. – Alboz

+4

Il n'est pas lié à Stream API son lié à la référence de méthode – rascio

+1

la solution canonique est '.map (s -> s.length() * 2)' car il n'y a aucune raison de l'exprimer comme deux fonctions juste pour les combiner immédiatement. Si vous avez déjà une instance de 'Function' ou si vous voulez en garder au moins une pour la réutiliser plus tard, le problème n'existe pas car alors vous avez un objet que vous pouvez appeler' andThen' ou 'compose'. Dans tous les autres cas, il n'y a aucune raison de le faire aussi compliqué. – Holger

Répondre

27

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 mettre dans un utilitaire classe et l'importer statiquement.

Vous pouvez également créer une méthode statique simple qui juste retourne la fonction qu'il a donné, pour le bien du compilateur savoir 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;  
    } 
} 
+3

+1 pour la seconde, si c'est vraiment juste pour éviter la distribution (j'allais suggérer juste 'fn (Stream :: longueur)') –

+0

Il n'est pas nécessaire de créer une méthode statique. Voir la réponse de JB Nizet ci-dessous. – pvillela

+0

@pvillela: Vrai (bien que vous ayez une variable séparée inutilement, ou une distribution laide). Va le laisser ici afin que toutes les options sont disponibles. –

4

Vous pouvez écrire:

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

Je sais mais parfois je voudrais concaténer des fonctions ... – rascio

+1

Il ne s'agit pas de l'API 'Stream' ... – rascio

17

Vous peut 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 il est moins lisible, l'OMI:

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

Vous devriez être en mesure d'obtenir ce que vous voulez en ligne en utilisant des moulages:

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

Il ne sont que « des conseils de type » pour le compilateur, donc ils ne sont pas réellement « jeter » l'objet et n'ont pas les frais généraux d'une distribution réelle.


Vous pouvez également utiliser une variable locale pour une meilleure 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 concis est 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.

+0

Donc, il n'y a pas de syntaxe de sucre pour l'avoir en ligne sans cast et parenthèse ... thx – rascio

+0

Voir mon édition pour un autre exemple. – Clashsoft

5

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 
6

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 de cible, et le contexte peut être - affectation, invocation de méthode, casting.

Si Function pourrait fournir une méthode statique juste pour le plaisir de taper 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'utiliser cette technique dans a functional interface

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

Syntaxe sugar 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:

Depuis AsyncIterator est 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 qui semble mieux que l'autre deux, et c'est le but de cette méthode.