[JAVA] Gegenseitige Umwandlung zwischen Funktion und Verbraucher

Einführung

Wenn Sie nur Nebenwirkungen ausführen möchten, weil Sie den Rückgabewert für die Methode verwerfen können, die Function als Argument verwendet, können Sie eine Variable vom Typ Consumer nicht direkt übergeben. Sie müssen es auf irgendeine Weise in einen Funktionstyp konvertieren.

Beachten Sie, dass in diesem Artikel der Einfachheit halber Funktion und Consumer als Beispiele verwendet werden. Alle Konvertierungen zwischen einer Funktionsschnittstelle, die einen Wert zurückgibt, und einer Funktionsschnittstelle, die keinen Wert zurückgibt, sind jedoch identisch. Wenn Sie eine andere Funktionsschnittstelle verwenden möchten, lesen Sie diese bitte entsprechend.

Konvertierungsbeispiel

Sie können es wie folgt konvertieren.

// Function<T, R>f an den Verbraucher<T>Umstellung auf
Consumer<T> c = f::apply;

// Consumer<T>c zu Funktion<T, Void>Umstellung auf
Function<T, Void> f = arg -> {
    c.accept(arg);
    return null;
};

Wenn Sie eine Methodenreferenz als Consumer-Typ übergeben, ist keine Konvertierung erforderlich, auch wenn es sich um einen Funktionstyp handelt, der einen Wert zurückgibt. Die obige Konvertierung verwendet auch diese Spezifikation (siehe unten).

Dienstprogrammmethode

Wenn Sie einen Consumer nicht sofort in eine Funktion konvertieren möchten, können Sie eine Dienstprogrammklasse erstellen und eine statische Methode für die Konvertierung bereitstellen.

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

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

Wenn das Ziel Ihre eigene Funktionsschnittstelle ist, ist es möglicherweise einfacher, es als Standardmethode zu definieren.

@FunctionalInterface
public static interface MyConsumer<T> {
    public abstract void doSomething(T t);
    public default Function<T, Void> asFunction() {
        return arg -> {
            doSomething(arg);
            return null;
        };
        //Wenn Sie auch die oben genannte Dienstprogrammklasse verwenden
        // return Functions.of(this::doSomething);
    }
}

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

Dieses Beispiel dient übrigens nur zur Demonstration. Verwenden Sie daher normalerweise forEach (System.out :: println). Nur für den Fall.

Neben 1 (Verschiedene Gedanken zu konkreten Beispielen und Design)

Ich schreibe oft solche Dienstprogrammmethoden, um das Auslassen von Ressourcen zu verhindern.

//Dateien können ohne Erlaubnis geschlossen werden#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);
    }
}

//Anwendungsbeispiel
Path path = Paths.get("example.txt");
Optional<String> head = safeLines(path, Stream::findFirst);
System.out.printf("Erste Linie: %s%n", head.orElse(""));

Wenn Sie eine solche Methode vorbereiten und diese Methode verwenden, wenn Sie Textdateien im Projekt verarbeiten, müssen Sie sich keine Sorgen machen, dass versehentlich ein Abschluss verpasst wird. Das Argument dieser Methode lautet jedoch Function, sodass Sie Consumer nicht übergeben können.

//Zum Beispiel, selbst wenn es eine solche Methode gibt
public class StreamUtils {
    public static void printAll(Stream<?> stream) {
        stream.forEach(System.out::println);
    }
}

//Dies führt zu einem Kompilierungsfehler
safeLines(path, StreamUtils::printAll);

//Müssen dies tun
safeLines(path, stream -> {StreamUtils.printAll(stream); return null;});

Wenn das Original ein Lambda ist, kann ich immer noch aufgeben, aber es ist mühsam, ein Lambda mit einem Block zu schreiben und null zurückzugeben, wenn es scheint, dass eine Methodenreferenz ausreicht.

Wenn Sie die Vorlagenseite ändern können, können Sie eine ähnliche Methode [^ 1] vorbereiten, die Consumer als Argument verwendet. In einigen Fällen können Sie sie jedoch nicht ändern und für jede Vorlage zwei Methoden vorbereiten [ Es wäre billiger, eine Konvertierungsmethode zu haben als ^ 2].

[^ 1]: Normalerweise ist die Methode mit demselben Namen überladen, aber wenn sie mit Lambda aufgerufen wird, ist der Methodenaufruf häufig mehrdeutig (es wird erforderlich, den Argumenttyp anzugeben). In diesem Fall lautet der Methodenname also Es ist sicherer zu teilen.

[^ 2]: Ich habe das Gefühl, dass ich es besser machen kann, indem ich es weiter abstrahiere, aber ich muss nicht so weit gehen. wurde.

Übrigens, wenn Sie die API der Methode ändern können, die Sie in der Methodenreferenz übergeben, können Sie den Rückgabetyp in Void anstatt in void ändern, aber ich bin mir nicht sicher, ob dies der richtige Weg ist.

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

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

Eigentlich ist dies vielseitiger, aber es ist immer noch keine allgemeine Schreibweise, und es ist ein subtiler Punkt, an dem Sie return null angeben müssen, schließlich.

Abgesehen von 2 (Fälle, die keine Konvertierung erfordern)

Wie eingangs erwähnt, ist je nach Übergabemethode möglicherweise keine Konvertierung erforderlich.

//Bei der Übergabe als Variable wird der Typ angegeben, sodass immer eine Konvertierung erforderlich ist.
Function<String, String> f = String::trim;

//Natürlich Kompilierungsfehler: Type mismatch
Consumer<String> c = f;

//Cast ist auch eine Laufzeitausnahme: ClassCastException
Consumer<String> c = (Consumer<String>) f;

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

//Wenn Sie es direkt als Methodenreferenz übergeben, können Sie es so lassen, wie es ist
Consumer<String> c = String::trim;

Weitere Informationen finden Sie in der Sprachspezifikation 15.13.2. Typ einer Methodenreferenz. bitte beziehen Sie sich auf. Wenn der empfangende Ergebnistyp ungültig ist, kann die Methode, die auf den Ergebnistyp verweist, grob beliebig sein. Andernfalls müssten sowohl der Empfänger als auch der Methodenreferent einen nicht ungültigen und kompatiblen Typ zurückgeben.

Lambda wird übrigens in 15.12.2.1. Identifizieren potenziell anwendbarer Methoden beschrieben. Ja, um es einfach auszudrücken

In beiden Fällen können Sie es ohne Konvertierung (oder Änderung) übergeben.

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

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

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

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

Nun, Lambda ist nicht wiederverwendbar, daher wird dieser Fehler nur angezeigt, wenn Sie ihn kopieren und einfügen.

Recommended Posts

Gegenseitige Umwandlung zwischen Funktion und Verbraucher
Konvertieren Sie mit Moshi zwischen Java-Objekten und JSON
Konvertierung zwischen Kotlin nullable und Java Optional
Hex und UIColor wechselseitige Konvertierung mit Swift
Zusammenfassung der gegenseitigen Konvertierung zwischen Groovys Standard-Groovy-Methoden und Javas Stream-API
Wechseln Sie zwischen JDK 7 und JDK 8
Unterschied zwischen vh und%
Unterschied zwischen i ++ und ++ i
Unterschied zwischen Produkt und Variante
Unterschied zwischen redirect_to und render
[Java] Unterschied zwischen == und gleich
Unterschied zwischen redirect_to und render
Unterschied zwischen CUI und GUI
Unterschied zwischen Variablen und Instanzvariablen
Beziehung zwischen Controller und Ansicht
Unterschied zwischen Mockito-Core und Mockito-All
Unterschied zwischen Klasse und Instanz
Unterschied zwischen Bundle und Bundle-Installation
Verbindung zwischen ViewModel und XML
Beziehung zwischen Paket und Klasse
Unterschied zwischen ArrayList und LinkedList
Unterschied zwischen render und redirect_to
Unterschied zwischen List und ArrayList
Unterschiede zwischen IndexOutOfBoundsException und ArrayIndexOutOfBoundsException
Unterschied zwischen .bashrc und .bash_profile
Unterschied zwischen StringBuilder und StringBuffer
Unterschied zwischen render und redirect_to