[Für Anfänger] So bedienen Sie die Stream-API nach Java 8

Interaktion mit der Stream-API nach Java 8

Einführung

Überblick

Es ist lange her, dass die Funktionsoberfläche in Java verfügbar wurde. Ich dachte, es sei ziemlich neu, aber es war vor fünf Jahren. Bei einer solchen Funktionsschnittstelle und Stream-API gibt es meines Erachtens einen erheblichen Unterschied zwischen denen, die "leicht zu verstehen" und denen, die "schwer zu verstehen" sagen. Ich werde es als Referenz für den Umgang mit der Stream-API in einer solchen Situation schreiben.

Annahme

Kurz gesagt, es scheint, als würden Leute, die noch keine Erfahrung mit Funktionsschnittstellen, Streams und Optional haben, mit Java 8 entwickeln.

Fazit

Der Punkt ist jedenfalls, dass wir aufhören, alles in die Methodenkette zu stopfen.

Faktoren, die das Verständnis des in der Stream-API geschriebenen Codes erschweren

Es gibt verschiedene Möglichkeiten, Lambda-Ausdrücke und Methodenreferenzen zu schreiben.

Es gibt verschiedene Möglichkeiten, ein Funktionsobjekt zu erstellen. Definieren Sie ein Funktionsobjekt, das eine Zeichenfolge verwendet und die oben definierten "StringUtils # isEmpty" aufruft, um das Ergebnis zurückzugeben.

Function<String, Boolean> emptyChecker1 = new Function<>{
  @Override
  public Boolean apply(String s) {
    return StringUtils.isEmpty(s);
  }
}

Function<String, Boolean> emptyChecker2 = s -> StringUtils.isEmpty(s);

Function<String, Boolean> emptyChecker3 = StringUtils::isEmpty;

Es ist besonders schwierig, auf eine Methode zu verweisen, und je nachdem, ob Sie den Namen der Klasse oder den Namen der Variablen links von :: schreiben, ändert sich die aufzurufende Methode und ist in einigen Fällen verwirrt.

Die Funktion des Arguments ist zu kompliziert, um es zu verstehen

Ich kann diesen Code nicht anzeigen, wenn ich ein Kind mit 1 Jahr Erfahrung in der Java-Entwicklung habe (Geständnis).

list.getValues().forEach(value -> {
  if (CollectionUtils.isEmpty(value.getPropertyList())) {
    return;
  }
  if (!CollectionUtils.isEmpty(value.getPropertyList())) {
    Property property = value.getPropertyList().stream()
        .findFirst()
        .orElseThrow(RuntimeException::new);
    service.insert(value, property);
    return;
  }

  switch (value.getType()) {
    case TYPE_1:
      value.getProperty1().setAmount(1000);
      break;
    case TYPE_2:
      value.getProperty2().setAmount(1000);
      break;
    case TYPE_3:
      value.getProperty3().setAmount(1000);
      break;
    }
    service.insert(value);
});

Ich versuche, den Quellcode mit dem Bewusstsein zu lesen, dass es für jeden Verarbeitungsblock einen Absatz gibt, aber wenn das Argument von "forEach" so lang ist, kann ich nicht alles auf einmal lesen, was mich unbehaglich macht. Ich werde. Ich bin im dritten Jahr, also bin ich sicher, dass die Kinder im ersten Jahr ...

Es ist ziemlich schwierig, eine Terminierungsverarbeitung zu schreiben

Wenn Sie nur in List konvertieren möchten, können Sie "Collectors.toList ()" verwenden, um es sofort zu entfernen, aber ich finde es ziemlich schwierig, einen einfachen Prozess zum Konvertieren von List in Map zu schreiben. Was tun bei Doppelarbeit? Für Anfänger sind die Hürden hoch und das Schreiben ist mühsam.

Map<Key, List<Value>> map = values.stream()
      .collect(Collectors.groupingBy(Value::getKey));

Regeln rund um die Stream-API (ich wollte in Zukunft arbeiten)

Wir haben einige Regeln unter der Annahme formuliert, dass viele unerfahrene Mitglieder eingeschrieben sind.

Schreiben Sie Zwischenoperationsargumente mit Methodenreferenzen

Der Zweck besteht darin, die Argumente der Zwischenoperationen "Stream # map" und "Stream # filter" einfach zu halten und die Lesbarkeit zu verbessern. Wir glauben, dass diese Regel die folgenden Vorteile hat:

Das dritte ist etwas schwer zu verstehen, daher werde ich es anhand eines Beispiels eines Programms erläutern, das die durchschnittliche Punktzahl der Halbzeitprüfung in der Klasse berechnet.

Übrigens, die Halbzeitprüfung setzt drei Fächer voraus, Japanisch, Mathematik und Englisch, und berücksichtigt die Implementierung von "ExaminationScoreSummary # durchschnitt".

public class ExaminationScore {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter
}

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    // TODO
  }
}

Bei Verwendung des Lambda-Ausdrucks

Es kann auf jede Weise implementiert werden. Ich schreibe es, als ob ich es immer schreibe.

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    return values.stream()
        .mapToInt(score -> score.getJapaneseScore() + score.getMathScore() + score.getEnglishScore())
        .average();  
  }
}

Nein, das ist in Ordnung, aber wenn Sie nach der Gesamtpunktzahl fragen, sollten Sie die Punkte jedes Mal beim Anrufer hinzufügen.

Bei Verwendung von Methodenreferenzen

Im Fall einer Methodenreferenz kann der Aufrufer nichts hinzufügen. Schreiben Sie daher vorerst den Prozess des Hinzufügens in die Klasse "ExaminationScore".

public class ExaminationScore {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter
  public Integer getTotalScore() {
    return japaneseScore + mathScore + englishScore;
  }
}

Für erfahrene Personen kann es natürlich sein, die Berechnung zwischen Feldern derselben Klasse in der Methode zu schreiben, in der die Felder definiert werden, um die Kohäsivität zu erhöhen. Aber auf meiner Ebene ist es schwierig. Es ist eine Geschichte, dass Sie, wenn Sie die Verwendung von Methodenreferenzen regieren, auch die Grundidee der objektorientierten Programmierung lernen.

Die Implementierung des Anrufers sieht folgendermaßen aus.

public class ExaminationScoreSummary() {
  private final List<ExaminationScore> values;
  // constractor, getter

  public Integer average() {
    return values.stream()
        .mapToInt(ExaminationScore::getTotalScore)
        .average();  
  }
}

Verwenden Sie java.util.Comparator, um mit mehreren Schlüsseln zu sortieren

Angenommen, Sie möchten in absteigender Reihenfolge der Prüfungsergebnisse zur Halbzeit posten. Nein, es gibt ein Problem wie das Belichten der Partitur ... Da es 3 Fächer gibt, werden sie in absteigender Reihenfolge der nationalen Sprachnoten sortiert. Wenn die nationalen Sprachnoten gleich sind, werden sie in absteigender Reihenfolge der mathematischen Noten sortiert.

Mit Comparator # compare und Comparator # thenComparing ist es möglich, Folgendes zu implementieren.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(Comparator.comparing(ExaminationScore::getJapaneseScore).thenComparing(ExaminationScore::getMathScore())
    .collect(Collectors.toList());

Es ist praktisch, Dinge so einfach sortieren zu können. Übrigens, wenn die Sortierreihenfolge kompliziert wird, schreibe ich hier alles? Nein, nein, es ist ziemlich hart. Wenn Sie zur Junior High School gehen und 5 Fächer haben oder wenn Sie eine Prüfung für praktische Fächer haben, wird es ein sehr langer Code sein.

Hier sind zwei Möglichkeiten, die Definition von "in welcher Reihenfolge" an anderer Stelle zu schreiben.

1. Lassen Sie die Elemente der Sammlung die Schnittstelle Comparable implementieren.

Auf diese Weise können Sie festlegen, wie nach dem Typ des Auflistungselements sortiert werden soll. Definieren Sie zunächst, wie sortiert werden soll. Es gibt nur zwei Schritte unten.

  1. Fügen Sie der Elementklassendeklaration implementiert Comparable <Elementklasse> hinzu
  2. Implementieren Sie "public int compareTo (Elementklasse o)" in der Elementklasse

Dieses Mal sind die Punktzahlen in der Reihenfolge der japanischen Punktzahl, der mathematischen Punktzahl und der englischen Punktzahl sortiert, daher habe ich sie wie folgt implementiert.

  1. Wenn die Ergebnisse der Landessprache nicht gleich sind, behandeln Sie das Vergleichsergebnis der Landessprache als Vergleichsergebnis "Prüfungsergebnisse".
  2. Wenn die Ergebnisse der Landessprache gleich sind und die mathematischen Ergebnisse nicht gleich sind, wird das Ergebnis des Vergleichs der mathematischen Ergebnisse als Vergleichsergebnis "Prüfungsergebnisse" behandelt.
  3. Wenn die Ergebnisse von Japanisch und Mathematik gleich sind und die Ergebnisse von Englisch gleich sind, wird das Vergleichsergebnis der englischen Ergebnisse als Vergleichsergebnis von "Prüfungsergebnis" behandelt.

// 1.In der Klassendeklaration des Elements`implements Comparable<Elementklasse>`Hinzugefügt
public class ExaminationScore implements Comparable<ExaminationScore> {
  private final Integer japaneseScore;
  private final Integer mathScore;
  private final Integer englishScore;

  // constractor, getter

  // 2.In der Elementklasse`public int compareTo(Elementklasse o)`Implementiert
  public int compareTo(ExaminationScore o) {
    if (japaneseScore.compareTo(o.japaneseScore) != 0) {
      return japaneseScore.compareTo(o.japaneseScore);
    }
    if (mathScore.compareTo(o.mathScore) != 0) {
      return mathScore.compareTo(o.mathScore);
    }
    return englishScore.compareTo(o.englishScore);
  }
}

Ich habe vergessen, was mit dem Rückgabewert "Comparable # compareTo" zurückgegeben werden soll, und versuche daher, dies so einfach wie möglich zu implementieren, indem ich das Vergleichsergebnis der Variablen zurückgebe, die der Schlüssel zum Sortieren sind.

Die Sortierung erfolgt wie folgt. Da es in absteigender Reihenfolge der Punktzahl sortiert wird, wird "Comparator # reverseOrder ()" aufgerufen.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());

Das Argument "Stream # sortiert" kann weggelassen werden, indem die Methode "Comparable # compareTo" als "-1, wenn die Punktzahl hoch ist" implementiert wird. Ich habe es jedoch vermieden, da es schwer zu verstehen ist.

2. Erstellen Sie eine separate Klasse, die die Comparator-Schnittstelle implementiert

Im Gegensatz zu 1 wird die Sortierreihenfolge in einer anderen Klasse definiert. Das Verfahren ist wie folgt:

  1. Erstellen Sie eine Klasse, die "Comparator " implementiert
  2. Implementieren Sie "public int compare (Elementklasse o1, Elementklasse o2)"
class ExaminationScoreComparator implements Comparator<ExaminationScore> {
  @Override
  public int compare(ExaminationScore o1, ExaminationScore o2) {
    if (Integer.compare(o1.getJapaneseScore(), o2.getJapaneseScore()) != 0) {
      return Integer.compare(o1.getJapaneseScore(), o2.getJapaneseScore());
    }
    if (Integer.compare(o1.getMathScore(), o2.getMathScore()) != 0) {
      return Integer.compare(o1.getMathScore(), o2.getMathScore());
    }
    return Integer.compare(o1.getEnglishScore(), o2.getEnglishScore());
  }
}

Die Sortierung erfolgt wie folgt. Übergeben Sie die oben im Argument "Stream # sortiert" definierte Instanz von Comparator.

List<ExaminationScore> values = new ArrayList<>();
values
    .stream()
    .sorted(new ExaminationScoreComparator().reverseOrder())
    .collect(Collectors.toList());

Comparable vs Comparator

Immerhin ist es eine Frage der zu verwendenden, aber im Grunde wollen wir es mit Comparator implementieren.

Für eine natürliche Bestellung ist keine Übereinstimmung mit Gleichen erforderlich, wird jedoch dringend empfohlen. Dies liegt daran, dass die Verwendung eines Sortiersatzes oder einer Sortierzuordnung, die keinen expliziten Komparator mit Elementen oder Schlüsseln angibt, deren natürliche Reihenfolge nicht mit gleich übereinstimmt, das Verhalten des Satzes und der Zuordnung nicht garantiert. Insbesondere verletzen solche Sortiersätze oder Sortierkarten die allgemeinen Regeln von Sätzen oder Karten. Diese Konvention wird unter Verwendung der Begriffe der Gleichheitsmethode definiert.

Von Vergleichbar (Java Platform SE 8)

Wie in der offiziellen Dokumentation angegeben, kann bei einem Konflikt zwischen "gleich" und "compareTo" der Betrieb mit "Karte" usw. nicht garantiert werden. Wenn die Klasse, die "Comparable" implementiert, der Schlüssel "Map" ist, verwendet "Map # get" das Ergebnis von "compareTo" anstelle des Schlüssels "equals", sodass Sie einen seltsamen Fehler haben.

Dasselbe wurde in Welcher Java-Vergleicher oder Komparator verwendet werden soll geschrieben.

Die kesselplattenartige Verarbeitung von "Stream # collect" wird an anderer Stelle herausgeschnitten

Ich denke, es ist üblich, ein bestimmtes Feld in einem Sammlungselement zu verwenden, um eine neue Sammlung zu erstellen, oder ein bestimmtes Feld in einer Sammlung als Schlüssel zum Erstellen einer Karte zu verwenden. Ich denke, es ist besser, solche Dinge verwenden zu können, ohne die Stream-API einzeln zu berühren.

/**
 *Erstellen Sie eine weitere Instanz aus dem Listenelement und geben Sie die Liste der erstellten Instanzen zurück.<br>
 *Die Instanzerstellungslogik folgt dem im zweiten Argument angegebenen Funktionsobjekt.<br>
 * @Parameterliste Liste
 * @Parametergenerator Ein Funktionsobjekt, das aus einem Element in der Liste eine Instanz eines anderen Typs erstellt
 * @param <S>Der Typ des ursprünglichen Listenelements.
 * @param <R>Neuer Listenelementtyp.
 * @return Liste der generierten Instanzen
 */
public static <S, R> List<Property> collect(List<S> list, Function<S, R> extractor){
  return list.stream()
      .map(extractor)
      .collect(Collectors.toList());
}

Von PropertyCollector.java

/**
 *Gruppieren Sie die Liste mit einem bestimmten Schlüssel und geben Sie eine Karte mit dem gepaarten Schlüssel und der Liste zurück.<br>
 *Die Schlüsselgenerierungslogik folgt dem im zweiten Argument angegebenen Funktionsobjekt.<br>
 * @Parameterliste Zu gruppierende Liste
 * @param keyExtractor Ein Funktionsobjekt, das einen Listenschlüssel von einem Listenelement erhält
 * @param <K>Schlüsselart. Muss eine Klasse sein, die die Schnittstelle Comparable implementiert
 * @param <V>Elementtyp auflisten
 * @Gruppierungsergebnis zurückgeben
 */
public static <K, V> Map<K, List<V>> groupingBy(List<V> list, Function<V, K> keyExtractor) {
  return list.stream().collect(Collectors.groupingBy(keyExtractor));
}

Von MapCollector.java

Schließlich

Die Funktionsoberfläche und die Stream-API sind sehr nützlich, aber für Teams mit vielen unerfahrenen Mitgliedern kann der Quellcode weniger lesbar und weniger produktiv sein. In diesem Artikel habe ich viel über den Witz geschrieben, über den ich im dritten Jahr nachgedacht habe, aber es scheint, dass das Team auch eine Schreibweise entwickelt und neue Java-Funktionen verwendet hat, um die Produktivität des Teams zu steigern. Ich würde mich freuen, wenn es so wäre.

Recommended Posts

[Für Anfänger] So bedienen Sie die Stream-API nach Java 8
[Ein Muss für einen Java-Ingenieurlehrling] Verwendung der Stream-API
[Java] Einführung in die Stream-API
Java-Anwendung für Anfänger: Stream
Java8 / 9-Anfänger: Streamen Sie API-Suchtpunkte und wie Sie damit umgehen
[java8] Um die Stream-API zu verstehen
[Einführung in Java] Informationen zur Stream-API
Java 8 ~ Stream API ~ startet jetzt
Für Java-Anfänger: Liste, Karte, Iterator / Array ... Wie konvertiere ich?
Java Stream API
[Ruby] Wie man Slice für Anfänger benutzt
[Für Anfänger] So debuggen Sie mit Eclipse
[Java] [Für Anfänger] So fügen Sie Elemente direkt in ein zweidimensionales Array ein
[Java] So testen Sie, ob es in JUnit null ist
[Für Anfänger] So implementieren Sie die Löschfunktion
Verwendung der Java-API mit Lambda-Ausdrücken
[Java] (für MacOS) Methode zur Einstellung des Klassenpfads
[Für Anfänger] Über Lambda-Ausdrücke und Stream-API
[Für Super-Anfänger] Verwendung des Autofokus: true
[Einführung in Java] Grundlagen der Java-Arithmetik (für Anfänger)
[Java] Wie man mehrere for-Schleifen einzeln macht
[Java] Stream API / Map
[Spring Boot] So erstellen Sie ein Projekt (für Anfänger)
[Für Anfänger] Mindestbeispiel für die Anzeige von RecyclerView in Java
Verwendung von Truth (Assertion Library für Java / Android)
Zusammenfassung der Java-Kommunikations-API (1) Verwendung von Socket
Einführung in Java für Anfänger Grundkenntnisse der Java-Sprache ①
Zusammenfassung der Java-Kommunikations-API (3) Verwendung von SocketChannel
Zusammenfassung der Java-Kommunikations-API (2) Verwendung von HttpUrlConnection
So schleifen Sie Java Map (für jede / erweiterte for-Anweisung)
Ausführen des WebCamCapture-Beispiels von NyARToolkit für Java
Wie man GitHub für Super-Anfänger benutzt (Teamentwicklung)
Verwendung der Ketten-API
So senken Sie die Java-Version
[Java] Verwendung von Map
So deinstallieren Sie Java 8 (Mac)
Java - So erstellen Sie JTable
Java-Debug-Ausführung [für Java-Anfänger]
[Java] Grundlegende Aussage für Anfänger
Java Stream API Spickzettel
Wie schreibe ich einen Java-Kommentar
Verwendung der Java-Klasse
Java Stream API in 5 Minuten
[Java] Verwendung von removeAll ()
[Java] So zeigen Sie Wingdings an
Verwendung von Java Map
[Java] Stream API - Stream-Beendigungsverarbeitung
So legen Sie Java-Konstanten fest
[Java] Stream API - Stream Zwischenverarbeitung
Java für Anfänger, Daten verstecken
Verwendung von Java-Variablen
So konvertieren Sie Java Base
[Java] So implementieren Sie Multithreading