Meine Aussicht auf Lambda -Ausdrücke in Java ist ziemlich verwirrt:
Ich denke, ich denke so: Lambda -Ausdrücke reduzieren die Leseerfahrung von Java -Programmen. Java -Programme waren in der Ausdruckskraft nie hervorragend. Im Gegenteil, einer der Faktoren, die Java populär machen, ist seine Sicherheit und sein Konservatismus-selbst Anfänger können robusten und leicht zu kämpfenden Code schreiben, solange sie darauf achten. Die Lambda -Ausdrücke haben relativ höhere Anforderungen für Entwickler, sodass sie auch einige Wartungsschwierigkeiten erhöhen.
Eine andere Sache, die ich denke, ist: Als Codecode ist es notwendig, neue Funktionen der Sprache zu lernen und zu akzeptieren. Wenn Sie seine ausdrucksstarken Stärken nur wegen seiner schlechten Leseerfahrung aufgeben, fällt es einigen Menschen schwer, selbst trinokulare Ausdrücke zu verstehen. Die Sprache entwickelt sich auch und diejenigen, die nicht Schritt halten können, werden freiwillig zurückgelassen.
Ich möchte nicht zurückgelassen werden. Wenn ich jedoch eine Wahl treffen musste, war meine Entscheidung immer noch relativ konservativ: Es besteht keine Notwendigkeit, Lambda in der Java -Sprache zu verwenden - viele Menschen im gegenwärtigen Java -Kreis sind nicht gewöhnt, und es wird zu einer Erhöhung der Arbeitskosten führen. Wenn Sie es sehr mögen, können Sie in Betracht ziehen, Scala zu verwenden.
Wie auch immer, ich begann immer noch zu versuchen, Lambda zu beherrschen, schließlich verwendet ein Teil des bei der Arbeit verwalteten Codes Lambda (vertrauen Sie mir, ich werde ihn allmählich entfernen). Die Tutorials zum Lernen sind verwandte Tutorials auf der offiziellen Oracle Java -Website.
- ~ - ~ ——————— ——— ——— ——— ——— ——— ——— ~
Angenommen, derzeit wird eine soziale Netzwerkanwendung erstellt. Eine Funktion ist, dass Administratoren bestimmte Aktionen für Mitglieder ausführen können, die die angegebenen Kriterien erfüllen, z. B. das Senden von Nachrichten. Die folgende Tabelle beschreibt diesen Anwendungsfall im Detail:
| Feld | beschreiben |
| Name | Aktionen ausführen |
| Hauptteilnehmer | Administrator |
| Voraussetzungen | Administratorleisten Sie sich beim System an |
| Post-Konditionen | Führen Sie nur Aktionen für Mitglieder aus, die die angegebenen Kriterien erfüllen |
| Haupt Erfolgsszenario | 1. Der Administrator legt die Filterstandards für die Zielmitglieder fest, um den Betrieb auszuführen. 2. Der Administrator wählt die auszuführende Aktion aus. 3. Der Administrator klickt auf die Schaltfläche Senden; 4. Das System findet Mitglieder, die die angegebenen Kriterien erfüllen; 5. Das System führt vorab ausgewählte Operationen bei Mitgliedern durch, die die angegebenen Kriterien erfüllen. |
| Erweitert | Vor der Auswahl der Ausführungsoperation oder vor dem Klicken auf die Schaltfläche Senden kann der Administrator auswählen, ob die Informationen zur Vorschau von Mitgliedern die Filterkriterien vorschreibt. |
| Auftretensfrequenz | Es passiert viele Male am Tag. |
Verwenden Sie die folgende Personklasse, um Mitgliederinformationen in sozialen Netzwerken darzustellen:
public class Person {public enum sex {männlich, weiblich} String Name; LOCALDATE Geburtstag; Sexualgeschlecht; String EmailAddress; public int getage () {// ...} public void printperson () {// ...}}Angenommen, alle Mitglieder werden in einer Liste <person> gespeichert.
In diesem Abschnitt beginnen wir mit einer sehr einfachen Methode und versuchen dann, sie mit lokalen Klassen und anonymen Klassen zu implementieren, und am Ende werden wir allmählich die Leistung und Effizienz von Lambda -Ausdrücken erleben. Der vollständige Code finden Sie hier.
Lösung 1: Erstellen Sie Methoden, um Mitglieder zu finden, die den angegebenen Kriterien nacheinander erfüllen
Dies ist die einfachste und raueste Lösung, um die oben genannten Fälle zu implementieren: Es wird mehrere Methoden erstellt und jede Methode überprüft ein Kriterium (wie Alter oder Geschlecht). Der folgende Code überprüft, ob das Alter älter ist als ein angegebener Wert:
public static void printpersonSolderthan (List <Person> Roster, int ay) {für (Person p: staaten) {if (p.getage ()> = älter) {p.printperson (); }}}Dies ist eine sehr fragile Lösung, und es ist sehr wahrscheinlich, dass die Anwendung aufgrund eines kleinen Updates nicht ausgeführt wird. Wenn wir der Personenklasse neue Mitgliedsvariablen hinzufügen oder den Algorithmus für die Messung des Alters im Standard ändern, müssen wir viel Code umschreiben, um diese Änderung anzupassen. Darüber hinaus sind die Einschränkungen hier zu starr. Was sollten wir beispielsweise tun, wenn wir Mitglieder drucken möchten, die jünger als ein bestimmter Wert sind? Fügen Sie eine weitere neue Methode hinzu, die Pressemittel für Pressemittel fügen? Dies ist offensichtlich eine dumme Methode.
Lösung 2: Erstellen Sie eine allgemeinere Methode
Die folgende Methode ist anpassungsfähiger als PrintPersonSolderthan. Diese Methode druckt Mitgliedsinformationen in der angegebenen Altersgruppe:
public static void printpersonsWitHinagerange (List <Person> Dienstplan, int niedrig, int hoch) {für (Person P: Roster) {if (low <= p.getage () && P.Getage () <hoch) {P.printperson (); }}}Jetzt gibt es eine neue Idee: Was sollen wir tun, wenn wir Mitgliederinformationen über das angegebene Geschlecht drucken möchten, oder das entspricht dem angegebenen Geschlecht und liegt innerhalb der angegebenen Altersgruppe? Was ist, wenn wir die Personklasse anpassen und Eigenschaften wie Freundschaft und geografische Lage hinzufügen? Obwohl das Schreiben solcher Methoden universeller ist als PrintPersonSyounggerthan, kann das Schreiben einer Methode für jede mögliche Abfrage auch zu Fragilität im Code führen. Es ist besser, den Standard -Check -Code in eine neue Klasse einzulegen.
Lösung 3: Implementieren Sie die Standardinspektion in einer lokalen Klasse
Die folgende Methode druckt Mitgliedsinformationen, die den Suchkriterien entsprechen:
public static void printporons (Liste <Person> Dienstplan, Checkperson Tester) {für (Person P: Roster) {if (tester.test (p)) {P.printperson (); }}}Im Programm wird ein CheckPerso -Objekt -Tester verwendet, um jede Instanz in der Liste der Listenparameter zu überprüfen. Wenn tester.test () true zurückgibt, wird die methode printperson () ausgeführt. Um die Suchkriterien festzulegen, muss die Checkperson -Schnittstelle implementiert werden.
Die folgende Klasse implementiert Checkperson und bietet eine spezifische Implementierung der Testmethode. Die Testmethode in dieser Klasse filtert Informationen zur Mitgliedschaft, die den Anforderungen für den Militärdienst in den USA entspricht: Das heißt, das männliche Geschlecht und Alter zwischen 18 und 25 Jahren.
class CheckPersonalLigibleForSelectiveService implements checkperson {public boolean test (Person p) {return P.Gender == Person.sex.Male && P.Getage ()> = 18 && P.getage () <= 25; }}Um diese Klasse zu verwenden, müssen Sie eine Instanz erstellen und die PrintPersons -Methode auslösen:
Printkörper (Dienstplan, neuer CheckPersonaleligibleForSelectiveService ());
Der Code sieht jetzt weniger zerbrechlich aus - wir müssen den Code aufgrund von Änderungen in der Struktur der Personenklassen nicht umschreiben. Hier gibt es jedoch noch zusätzlichen Code: eine neu definierte Schnittstelle, die eine interne Klasse für jeden Suchstandard in der Anwendung definiert.
Da checkPersonaleligibleForSelectiveService eine Schnittstelle implementiert, kann eine anonyme Klasse verwendet werden, ohne eine innere Klasse für jeden Standard zu definieren.
Lösung 4: Verwenden Sie anonyme Klassen, um die Standardinspektion zu implementieren
Ein Parameter in der unten aufgerufenen PrintPersons -Methode ist die anonyme Klasse. Die Funktion dieser anonymen Klasse ist die gleiche wie die der CheckpersonalLigibleForSelectiveService -Klasse in Schema 3: Sie sind alle gefilterte Mitglieder mit männlichem Geschlecht und Alter zwischen 18 und 25 Jahre alt.
printporsons (Dienstplan, neuer checkperson () {public boolean test (Person p) {return p.getGender () == Person.sex.Male && P.Getage ()> = 18 && P.getage () <= 25;}});Dieses Schema reduziert die Menge an Codierung, da keine neuen Klassen mehr für jedes Suchschema erstellt werden müssen. Es ist jedoch immer noch ein bisschen unangenehm, dies zu tun: Obwohl die Checkperson -Schnittstelle nur eine Methode hat, ist die implementierte anonyme Klasse immer noch ein bisschen ausführlich und sperrig. Zu diesem Zeitpunkt können Sie den Lambda -Ausdruck verwenden, um anonyme Klassen zu ersetzen. Im Folgenden wird erklärt, wie der Lambda -Ausdruck verwendet wird, um anonyme Klassen zu ersetzen.
Lösung 5: Verwenden Sie Lambda -Ausdrücke, um die Standardprüfung zu implementieren
Die Checkperson -Schnittstelle ist eine funktionale Schnittstelle. Die sogenannte funktionale Schnittstelle bezieht sich auf eine Schnittstelle, die nur eine abstrakte Methode enthält. (Eine funktionale Schnittstelle kann auch mehrere Standardmethoden oder statische Methoden aufweisen). Da in der funktionalen Schnittstelle nur eine abstrakte Methode vorhanden ist, kann der Methodenname der Methode bei der Implementierung der Methode dieser funktionalen Schnittstelle weggelassen werden. Um diese Idee zu implementieren, können Sie anonyme Klassenausdrücke durch Lambda -Ausdrücke ersetzen. Im Folgenden wird der entsprechende Code hervorgehoben:
printporsons (Dienstplan, (Person p) -> p.getGender () == Person.sex.Male && P.Getage ()> = 18 && P.Getage () <= 25);
Hier können Sie auch eine Standardfunktionsschnittstelle verwenden, um die Checkperson -Schnittstelle zu ersetzen und so den Code weiter zu vereinfachen.
Lösung 6: Verwenden Sie Standardfunktionsoberflächen in Lambda -Ausdrücken
Schauen wir uns die Checkperson -Schnittstelle an:
Schnittstelle Checkperson {Boolean Test (Person p); }Dies ist eine sehr einfache Schnittstelle. Da es nur eine abstrakte Methode gibt, handelt es sich auch um eine funktionale Schnittstelle. Diese abstrakte Methode akzeptiert nur einen Parameter und gibt einen booleschen Wert zurück. Diese abstrakte Schnittstelle ist so einfach, dass wir überlegen, ob es notwendig ist, eine solche Schnittstelle in der Anwendung zu definieren. Zu diesem Zeitpunkt können Sie in Betracht ziehen, die von JDK definierten Standardfunktionsschnittstellen zu verwenden, und diese Schnittstellen finden Sie unter dem Paket von Java.util.Function.
In diesem Beispiel können wir die Prädikat -Schnittstelle zum Ersetzen von Checkperson verwenden. In dieser Schnittstelle befindet sich eine Boolesche Test (T T):
Schnittstelle Prädikat <T> {boolean Test (t t); }Die Prädikat -Schnittstelle ist eine generische Schnittstelle. Eine generische Klasse (oder eine generische Schnittstelle) gibt einen oder mehrere Typparameter mit einem Paar Winkelklammern (<>) an. In dieser Schnittstelle gibt es nur einen Typparameter. Wenn Sie eine generische Klasse mit einer konkreten Klasse deklarieren oder instanziieren, erhalten Sie eine parametrisierte Klasse. Beispielsweise ist das parametrisierte Klassenprädikat <person> wie folgt:
Schnittstellenprädikat <Person> {Boolean Test (Person t); }In dieser parametrisierten Klasse gibt es eine Methode, die mit den Parametern und Rückgabewerten der Methode der Checkperson.boolean Test (Person P) übereinstimmt. Daher können Sie die Prädikat -Schnittstelle verwenden, um die Grenzfläche für Checkperson zu ersetzen, wie in der folgenden Methode gezeigt:
public static void printpersonsWithPredicate (Liste <Person> Dienstplan, Predicate <Person> Tester) {für (Person P: Roster) {if (tester.test (p)) {p.printperson (); }}}Verwenden Sie dann den folgenden Code, um Mitglieder des Militärdienstes wie in Plan 3 zu filtern:
printpersonsWithPredicate (Roster, p -> p.getGender () == Person.sex.Male && P.getage ()> = 18 && P.Getage () <= 25);
Haben Sie festgestellt, dass bei Verwendung von Prädikat <person> als Parametertyp kein expliziter Parametertyp angegeben wird. Dies ist nicht der einzige Ort, an dem Lambda -Ausdrücke angewendet werden. Das folgende Schema wird mehr Nutzung von Lambda -Ausdrücken einführen.
Lösung 7: Verwenden Sie Lambda -Ausdrücke in der gesamten Anwendung
Werfen wir einen Blick auf die Pressemarch -Methode und prüfen Sie, ob Sie hier Lambda -Ausdrücke verwenden können:
public static void printpersonsWithPredicate (Liste <Person> Dienstplan, Predicate <Person> Tester) {für (Person P: Roster) {if (tester.test (p)) {p.printperson (); }}}Bei dieser Methode wird jede Personinstanz in der Liste mit dem Prädikatinstanz -Tester überprüft. Wenn die Person Instanz den im Tester definierten Scheckkriterien entspricht, wird die Printärmethode der Personinstanz ausgelöst.
Zusätzlich zur Auslösung der PrintPerson -Methode können Personeninstanzen, die den Tester -Standard erfüllen, auch andere Methoden ausführen. Sie können in Betracht ziehen, einen Lambda -Ausdruck zu verwenden, um die auszuführende Methode anzugeben (ich denke, diese Funktion ist gut, was das Problem löst, dass Methoden in Java nicht als Objekte übergeben werden können). Jetzt benötigen Sie einen Lambda -Ausdruck, der der PrintPerson -Methode ähnelt - einen Lambda -Ausdruck, der nur einen Parameter benötigt und ungehindert zurückgibt. Denken Sie an eine Sache: Um Lambda -Ausdrücke zu verwenden, müssen Sie zuerst eine funktionale Schnittstelle implementieren. In diesem Beispiel ist eine funktionale Schnittstelle erforderlich, die nur eine abstrakte Methode enthält. Diese abstrakte Methode hat einen Parameter der Typ Person und kehrt zu void zurück. Sie können einen Blick auf die von JDK bereitgestellte Standardfunktionsschnittstellenverbraucher -Verbraucher -Verbraucher ansehen, die eine abstrakte Methode aufweist. Verwenden Sie im folgenden Code eine Instanz von Verbraucher <t>, um die Akzeptanzmethode anstelle von P.Printperson () aufzurufen:
public static void ProcessPersons (Liste <Person> Dienstplan, Prädikat <Person> Tester, Verbraucher <Person> block) {für (Person P: Roster) {if (tester.test (p)) {block.accept (p); }}}Entsprechend können Sie den folgenden Code verwenden, um Mitglieder des Militärdienstes zu filtern:
ProcessPortons (Roster, p -> p.getGender () == Person.sex.Male && P.Getage ()> = 18 && P.getage () <= 25, p -> p.printperson ());
Wenn wir Dinge tun möchten, die nicht nur Mitgliederinformationen drucken, sondern mehr Dinge, wie z. B. die Überprüfung der Mitgliedschaft, das Erhalten von Mitgliedsinformationen usw. Zu diesem Zeitpunkt benötigen wir eine funktionale Schnittstelle mit einer Rückgabewertmethode. Die Standardfunktionsschnittstellenfunktion von JDK <T, R> verfügt über eine Methode wie diese R -Anwendung (T T). Die folgende Methode erhält Daten vom Parameter Mapper und führt das durch den Parameterblock für diese Daten angegebene Verhalten aus:
public static void processPersons -withfunction (Liste <Person> Dienstplan, Prädikat <Person> Tester, Funktion <Person, String> Mapper, Verbraucher <string> block) {für (Person p: Dienstplan) {if (tester.test (p)) {String data = mapper.apply (p); block.accept (Daten); }}}Der folgende Code erhält die E -Mail -Informationen aller Mitglieder des Militärdienstes in Kader und druckt sie aus:
ProcessPersonsWithfunction (Roster, p -> p.getGender () == Person.sex.Male && P.getage ()> = 18 && P.Getage () <= 25, p -> P.Getemailaddress (), E -Mail -> System.out.println (E -Mail));
Lösung 8: Generika häufiger verwenden
Lassen Sie uns die ProcessPersons -Withfunction -Methode überprüfen. Das Folgende ist eine generische Version dieser Methode. Die neue Methode erfordert mehr Toleranz bei Parametertypen:
public static <x, y> void -Prozesselemente (iterable <x> Quelle, Prädikat <x> Tester, Funktion <x, y> mapper, Verbraucher <y> block) {für (x p: Quelle) {if (tester.test (p)) {y data = mapper.apply (p); block.accept (Daten); }}}Um Mitgliederinformationen für den Militärdienst im richtigen Alter zu drucken, können Sie die Prozesselementmethode wie folgt aufrufen:
ProzessElements (Roster, p -> p.getGender () == Person.sex.Male && P.Getage ()> = 18 && P.Getage () <= 25, p -> P.Getemailaddress (), E -Mail -> System.out.println (E -Mail));
Während des Methodenaufrufprozesses wird das folgende Verhalten durchgeführt:
Rufen Sie Objektinformationen aus einer Sammlung in diesem Beispiel an, um Personen -Objektinformationen aus der Sammlungsinstanzliste zu erhalten.
Filterobjekte, die mit dem Prädikatinstanz -Tester übereinstimmen können. In diesem Beispiel ist das Prädikatobjekt ein Lambda -Ausdruck, der die Bedingungen für die Filterung des Militärdienstes im richtigen Alter angibt.
Das gefilterte Objekt wird an einen Funktionsobjekt -Mapper zur Verarbeitung übergeben, und der Mapper stimmt mit einem Wert dieses Objekts überein. In diesem Beispiel ist der Funktionsobjekt Mapper ein Lambda -Ausdruck, der die E -Mail -Adresse jedes Mitglieds zurückgibt.
Gibt ein Verhalten des Verbraucherobjektblocks für den vom Mapper übereinstimmenden Wert an. In diesem Beispiel ist das Verbraucherobjekt ein Lambda -Ausdruck, das die Funktion des Druckens einer Zeichenfolge ist. Dies ist die von der Funktionsinstance Mapper zurückgegebene Mitglieds -E -Mail -Adresse.
Lösung 9: Verwenden Sie den Aggregationsbetrieb unter Verwendung des Lambda -Expression als Parameter
Der folgende Code verwendet den Aggregationsbetrieb, um die E-Mail-Adressen von Mitgliedern des Militäralters in der Kadersammlung zu drucken:
Roster.Stream () .Filter (p -> p.getGender () == Person.sex.Male && P.Getage ()> = 18 && P.Getage () <= 25).
Analysieren Sie den Ausführungsprozess des oben genannten Code und organisieren Sie die folgende Tabelle:
Verhalten | Aggregationsoperation |
Holen Sie sich das Objekt | Stream <E> stream () |
Filterobjekte, die den angegebenen Kriterien der Prädikatinstanz entsprechen | Stream <T> Filter (Prädikat <? Super t> Vorhersage) |
Rufen Sie den passenden Wert des Objekts über eine Funktionsinstanz ab | <R> Stream <R> MAP (Funktion <? Super t, erweitert R> mapper) |
Führen Sie das durch die Verbraucherinstanz angegebene Verhalten aus | Leere für Each (Verbraucher <? Super t> Aktion) |
Der Filter, die Karte und die Foreach -Operationen in der Tabelle sind alle aggregierten Operationen. Die von der Aggregationsoperation verarbeiteten Elemente stammen aus dem Stream, nicht direkt aus der Sammlung (dh die in diesem Beispiel aufgerufene erste Methode lautet Stream ()). Ein Stream ist eine Datensequenz. Im Gegensatz zu Sammlungen speichert Stream keine Daten mit einer bestimmten Struktur. Stattdessen erhält Stream Daten von einer bestimmten Quelle, z. B. aus einer Sammlung, über eine Pipeline. Pipeline ist eine Stream-Betriebssequenz, in diesem Beispiel Filter-Map-Foreach. Darüber hinaus verwenden Aggregationsvorgänge in der Regel Lambda -Ausdrücke als Parameter, was uns auch viel benutzerdefinierten Platz bietet.