[JAVA] Conversion mutuelle entre fonction et consommateur

introduction

Si vous souhaitez exécuter uniquement des effets secondaires car vous pouvez ignorer la valeur de retour de la méthode qui prend Function comme argument, vous ne pouvez pas passer directement une variable de type Consumer. Vous devez le convertir en un type de fonction d'une manière ou d'une autre.

Notez que cet article utilise Function et Consumer comme exemples de simplicité, mais toutes les conversions entre une interface fonctionnelle qui renvoie une valeur et une interface fonctionnelle qui ne renvoie pas sont identiques. Si vous souhaitez utiliser une autre interface fonctionnelle, veuillez la lire comme il convient.

Exemple de conversion

Vous pouvez le convertir comme suit.

// Function<T, R>f au consommateur<T>Conversion en
Consumer<T> c = f::apply;

// Consumer<T>c à Fonction<T, Void>Conversion en
Function<T, Void> f = arg -> {
    c.accept(arg);
    return null;
};

Si vous transmettez une référence de méthode en tant que type Consumer, aucune conversion n'est requise même s'il s'agit d'un type de fonction qui renvoie une valeur. La conversion ci-dessus utilise également cette spécification (voir ci-dessous).

Méthode utilitaire

Si vous ne souhaitez pas convertir un consommateur en fonction à la volée, vous pouvez créer une classe utilitaire et fournir une méthode statique pour la conversion.

public class Functions {
    public static <T> Function<T, Void> of(Consumer<T> consumer) {
        return arg -> {
            consumer.accept(arg);
            return null;
        };
    }
}

//Exemple d'utilisation
Stream.of("1","2","3").map(Functions.of(System.out::println)).forEach(v -> {});

Si la cible est votre propre interface fonctionnelle, il peut être plus facile de la définir comme méthode par défaut.

@FunctionalInterface
public static interface MyConsumer<T> {
    public abstract void doSomething(T t);
    public default Function<T, Void> asFunction() {
        return arg -> {
            doSomething(arg);
            return null;
        };
        //Si vous utilisez également la classe utilitaire ci-dessus
        // return Functions.of(this::doSomething);
    }
}

//Exemple d'utilisation
MyConsumer<String> m = System.out::println;
Stream.of("a","b","c").map(m.asFunction()).forEach(v -> {});

À propos, cet exemple est juste pour la démonstration, utilisez donc généralement forEach (System.out :: println). Au cas où.

A côté 1 (Exemples spécifiques et diverses réflexions sur la conception)

J'écris souvent des méthodes utilitaires comme celle-ci pour éviter les omissions de fermeture des ressources.

//Fichiers à fermer sans autorisation#lines()
public static <R> R safeLines(Path path, Function<Stream<String>, R> lineProcessor) throws IOException {
    try(Stream<String> stream = Files.lines(path)) {
        return lineProcessor.apply(stream);
    }
}

//Exemple d'utilisation
Path path = Paths.get("example.txt");
Optional<String> head = safeLines(path, Stream::findFirst);
System.out.printf("Première ligne: %s%n", head.orElse(""));

Si vous préparez une telle méthode et utilisez cette méthode chaque fois que vous gérez des fichiers texte dans le projet, vous n'avez pas à vous soucier de manquer accidentellement une fermeture. Cependant, l'argument de cette méthode est Function, vous ne pouvez donc pas passer Consumer.

//Par exemple, même s'il existe une méthode comme celle-ci
public class StreamUtils {
    public static void printAll(Stream<?> stream) {
        stream.forEach(System.out::println);
    }
}

//Cela entraînera une erreur de compilation
safeLines(path, StreamUtils::printAll);

//Besoin de faire ça
safeLines(path, stream -> {StreamUtils.printAll(stream); return null;});

Si l'original est un lambda, vous pouvez toujours abandonner, mais c'est pénible d'écrire un lambda avec un bloc et de renvoyer null; quand il semble que vous pouvez simplement vous référer à la méthode.

Si vous pouvez changer le côté du modèle, vous pouvez préparer une méthode similaire [^ 1] qui prend Consumer comme argument, mais bien sûr il y a des cas où vous ne pouvez pas le changer, et préparer deux méthodes pour chaque modèle [ Il serait moins cher d'avoir une méthode de conversion que ^ 2].

[^ 1]: Normalement, la méthode avec le même nom est surchargée, mais lorsqu'elle est appelée avec lambda, l'appel de méthode est souvent ambigu (il devient nécessaire de spécifier le type d'argument), donc dans ce cas le nom de la méthode est Il est plus sûr de se diviser.

[^ 2]: Je sens que je peux me sentir mieux en l'abstruisant davantage, mais je n'ai pas à aller aussi loin. devenu.

À propos, si vous pouvez modifier l'API de la méthode que vous transmettez dans la référence de méthode, vous pouvez modifier le type de retour en Void au lieu de void, mais je ne suis pas sûr que ce soit la voie à suivre.

//fais ça
public class StreamUtils {
    public static Void printAll(Stream<?> stream) {
        stream.forEach(System.out::println);
        return null;
    }
}

//Traverser
safeLines(path, StreamUtils::printAll);

En fait, c'est plus polyvalent, mais ce n'est toujours pas une manière générale d'écrire, et c'est un point subtil que vous devez spécifier return null; après tout.

À part 2 (cas qui ne nécessitent pas de conversion)

Comme mentionné au début, la conversion peut ne pas être nécessaire en premier lieu selon la méthode de passage.

//Lors du passage en tant que variable, le type est spécifié, donc la conversion est toujours requise.
Function<String, String> f = String::trim;

//Erreur de compilation bien sûr: Type mismatch
Consumer<String> c = f;

//Cast est également une exception à l'exécution: ClassCastException
Consumer<String> c = (Consumer<String>) f;

//Traverser
Consumer<String> c = f::apply;

//Si vous le transmettez directement comme référence de méthode, vous pouvez le laisser tel quel
Consumer<String> c = String::trim;

Pour plus de détails, reportez-vous à la spécification du langage 15.13.2. Type de référence de méthode. prière de se référer à. En gros, si le type de résultat de réception est void, le type de résultat de référence de la méthode peut être n'importe quoi. Sinon, le destinataire et la référence de méthode doivent renvoyer un type non void et compatible.

En passant, lambda est décrit dans 15.12.2.1. Identifier les méthodes potentiellement applicables. Oui, pour le dire simplement

--Si le corps lambda est une expression, il peut être traité comme une instruction. --Si le corps lambda est un bloc, la valeur n'est pas renvoyée

Dans les deux cas, vous pouvez le transmettre sans conversion (ni modification).

//Erreur de compilation: Void methods cannot return a value
Consumer<String> c = s -> s;

//Traverser
Consumer<String> c = s -> s.trim();

//Erreur de compilation: Void methods cannot return a value
Consumer<String> c = s -> {return s.trim();};

//Traverser
Consumer<String> c = s -> {s.trim(); return;};

Eh bien, Lambda n'est pas réutilisable, vous n'obtiendrez donc pas cette erreur à moins de la copier et de la coller.

Recommended Posts

Conversion mutuelle entre fonction et consommateur
Conversion entre objets Java et JSON à l'aide de Moshi
Conversion entre Kotlin nullable et Java facultative
Conversion mutuelle Hex et UIColor avec Swift
Résumé de la conversion mutuelle entre les méthodes Groovy par défaut de Groovy et l'API Stream de Java
Basculer entre JDK 7 et JDK 8
Différence entre vh et%
Différence entre i ++ et ++ i
Différence entre produit et variante
Différence entre redirect_to et render
[Java] Différence entre == et égal
Différence entre redirect_to et render
Différence entre CUI et GUI
Différence entre les variables et les variables d'instance
Relation entre le contrôleur et la vue
Différence entre mockito-core et mockito-all
Différence entre classe et instance
Différence entre l'installation de bundle et de bundle
Connexion entre ViewModel et XML
Relation entre le package et la classe
Différence entre ArrayList et LinkedList
Différence entre render et redirect_to
Différence entre List et ArrayList
Différences entre IndexOutOfBoundsException et ArrayIndexOutOfBoundsException
Différence entre .bashrc et .bash_profile
Différence entre StringBuilder et StringBuffer
Différence entre render et redirect_to