Agenda

  • Intro Collections
  • Java Stream API
  • Quellen
  • Intermediate Operations
  • Terminal Operations

Intro Collections

Collection

  • ArrayList<Student>
  • ArrayList<Car>
  • ArrayList<Animal>

Collections bieten einen direkten Zugriff auf die Elemente um Sie zu verwalten.

Collection II

  • Daten abfragen → Name des ältesten Studenten
  • Daten ändern → Preis eines Produkts erhöhen

Was ist ein Java Stream?

Eine Sequenz (Abfolge) von Elementen, die funktionale Operationen (Funktionale Interfaces) unterstützt, um Daten zu verarbeiten, transformieren und aggregieren

Streams vs Collection

  • Streams manipulieren keine Daten (immutable)
  • Streams verarbeiten Daten nach Bedarf (lazy)
  • Streams verarbeiten Daten parallel

Demo - Intro Stream API

  • Anzahl Studenten
  • & Älter als 24
  • & Vorname mindestens 4 Zeichen
  • & Fullname mehr als 10 Zeichen
  • gleiches als Stream

Java Stream API

Was is eine Stream Pipeline

public class Main {
  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    
    names.stream() // source
      .filter(name -> name.length > 4) //inter-
      .map(name -> name.toUpperCase()) //mediate
      .limit(12)                      //operations
      .forEach(System.out::println); // terminal operation
  }
}

Charakteristika einer Stream Pipeline

  • Intermediate Operations sind optional
  • Terminal Operation ist erforderlich
  • Terminal Operation führt die Pipeline aus
  • Pipeline kann nur einmal genutzt werden

Demo - Stream API

  • Intermediate Optional
  • Terminal erforderlich, sonst passiert nichts
  • Pipeline nur einmal Nutzbar
  • Intermediate Reihenfolge

Aufbau einer Pipeline

  • Quelle
  • Intermediate Operations
  • Terminal Operations

Quellen

Erzeugen von Quellen I

public class Main {
  public static void main(String[] args) {
    // Collection.stream(); // interface
    // → Klassen die Collection implementieren:
    ArrayList<Student> students = new ArrayList<>();
    students.stream();
    
    HashMap<String, Student> map = new HashMap<>();
    map.keySet().stream();
    map.entrySet().stream();
    map.values().stream();
  }
}

Erzeugen von Quellen II

public class Main {
  public static void main(String[] args) {
    // Array in ein Stream konvertieren:
    // Arrays.stream(T[])
    Stream<Integer> num1 = Arrays.stream({ 1, 2, 3, 4 });
    
    int[] numArray = { 1, 2, 3, 4 };
    Stream<Integer> num2 = Arrays.stream(numArray);
  }
}

Erzeugen von Quellen III

public class Main {
  public static void main(String[] args) {
    // Gleichartige Werte in ein Stream kovertieren:
    // Stream.of(T...);
    Stream<Integer> num1 = Stream.of(1, 2, 3, 4);
  }
}

Intermediate Operations

Intermediate Operations

sind Methoden eines Streams, die als Rückgabewert einen Stream zurückgeben.

Stream Klasse

filter - Methode

Stream<T> filter(Predicate<? super T> predicate)

Der Parameter predicate muss das Predicate Interface implementieren.

filter - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .filter(number -> number > 3);
      // nur 4 bleibt übrig
  }
}

map - Methode

<R> Stream<R> map(Function<? super T,? extends R> mapper)

Der Parameter mapper muss das Function Interface implementieren.

Die Eingabe vom Typ T definiert der vorherige Stream. Der Rückgabetyp des mapper Parameters definiert den Rückgabetyp des Streams.

map - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .map(number -> number * 2);
    // Rückgabetyp: Stream<Integer>
    Stream.of(1, 2, 3, 4)
      .map(number -> String.valueOf(number));
    // Rückgabetyp: Stream<String>
  }
}

limit - Methode

Stream<T> limit(long maxSize)

Es werden maximal "maxSize" Elemente des vorherigen Streams weitergegeben.

limit - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .limit(2);
      // nur 1 & 2 werden weitergegeben
  }
}

skip - Methode

Stream<T> skip(long n)

Es werden n-Elemente übersprungen.

skip - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .skip(2);
      // nur 3 & 4 werden weitergegeben
  }
}

sorted - Methode

Stream<T> sorted(Comparator<? super T> comparator)

Der Parameter comparator muss das Comparator Interface implementieren.

sorted - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(4, 3, 2, 1)
      .sorted((n1, n2) -> Integer.compare(n1, n2));
      // 1, 2, 3, 4
      // Sagt Bye Bye zu Collections.sort()
  }
}

distinct - Methode

Stream<T> distinct()

Es werden nur einzigartige Werte im Stream beibehalten. Diese werden Mithilfe von .equals identifiziert.

distinct - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 1, 4)
      .distinct();
      // nur 1, 2 & 4 werden weitergegeben
  }
}

Von Stream zu Stream

Intermediate Operations werden auf einem Stream aufgerufen und geben immer einen Stream zurück.

Demo - Lambda Funktionen Auslagern

  • Review von Stream Api Examples
  • Attribut: minimumFirstName
  • Attribut: olderThan24Years
  • Attribut: toFullName
  • Methode: olderThanYears
  • Methode: fullNameIsLongerThan

Terminal Operations

Terminal Operations

  • Matching und Suchen
  • Transformationen
  • Statistik
  • Verarbeitung

Matching

Mit Matching kann abgefragt werden ob bestimmte Elemente einer Bedingung entsprechen.

Matching - Methoden

boolean  allMatch(Predicate<T> predicate) // alle
boolean noneMatch(Predicate<T> predicate) // keiner
boolean  anyMatch(Predicate<T> predicate) // mindestens einer

Matching - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .allMatch(number -> number > 3); // false
    
    Stream.of(1, 2, 3, 4)
      .noneMatch(number -> number > 4); // true
    
    Stream.of(1, 2, 3, 4)
      .anyMatch(number -> number > 2); // true
  }
}

Suchen

Mit findAny und findFirst wird das erste Element in einem Stream zurückgegeben.

Suchen - Methoden

Optional<T> findAny() // nicht deterministisch
Optional<T> findFirst() // deterministisch

Hauptsächlich wichtig bei parallelen Streams

Suchen - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .filter(number -> number > 1)
      .findAny() // 2, 3 oder 4
    
    Stream.of(1, 2, 3, 4)
      .filter(number -> number > 1)
      .findFirst() // immer 2
  }
}

Transformationen

Die Ergebnismenge wird gesammelt.

Transformationen - Methoden

List<T> toList()
T[] toArray()

T reduce(T identity, BinaryOperator<T> accumulator)

R collect(Collector<T,A,R> collector)

Transformationen - Verwendung I

public class Main {
  public static void main(String[] args) {
    List<Integer> nums = Stream.of(1, 2, 3, 4)
      .filter(number -> number > 1)
      .toList() // List<Integer>
    
    Object[] nums2 = Stream.of(1, 2, 3, 4)
      .filter(number -> number > 1)
      .toArray() // Object[]
  }
}

Transformationen - Verwendung II

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .reduce(0, (a, b) -> a + b);  // int *NKR
  }
}

Transformationen - Verwendung III

public class Main {
  public static void main(String[] args) {
    ArrayList<Student> students = getManyStudents()
      .stream()
      .collect(Collectors.toList());
      // Collectors.toMap ist Klausurrelevant
      // Collectors.groupingBy ist Klausurrelevant
  }
}

Demo - Collectors

Statistik

Mit Statistik Operationen lassen sich Anzahl, Minimum, Maximum, Summe und Durchschnitt berechnen.

Statistik - Methoden

long count()

Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)

Statistik - Verwendung I

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .count(); // 4
  }
}

Statistik - Verwendung II

public class Main {
  public static void main(String[] args) {
    Optional<Integer> min = Stream.of(1, 2, 3, 4)
      .min((n1, n2) -> Integer.compare(n1, n2));
    
    min.ifPresent(System.out::println); // 1
  }
}

Statistik - Verwendung III

public class Main {
  public static void main(String[] args) {
    Optional<Integer> max = Stream.of(1, 2, 3, 4)
      .max((n1, n2) -> Integer.compare(n1, n2));
    
    max.ifPresent(System.out::println); // 4
  }
}

Statistik Streams Erzeugen

Für die Methoden Durchschnitt und Summe werden spezifische Streams benötigt:

  • IntStream
  • LongStream
  • DoubleStream

Statistik Streams Erzeugen - Methoden

Um einen Statistik Stream zu erzeugen gibt es Intermediate Operations

DoubleStream mapToDouble(ToDoubleFunction<T> mapper)
IntStream    mapToInt(ToIntFunction<T> mapper)
LongStream   mapToLong(ToLongFunction<T> mapper)

Statistik Streams Erzeugen - Verwenden

public class Main {
  public static void main(String[] args) {
    ArrayList<Student> students = getManyStudents();
    IntStream studentAges = students.stream()
      .mapToInt(student -> student.age());
  }
}

Statistik Streams - Methoden

long sum()
                           
OptionalDouble average()

Statistik Streams - Verwendung I

public class Main {
  public static void main(String[] args) {
    IntStream manyNumbers = getManyNumbers();
    long sum = manyNumbers.sum();
  }
}

Statistik - Verwendung II

public class Main {
  public static void main(String[] args) {
    IntStream manyNumbers = getManyNumbers();
    manyNumbers.average()
      .ifPresent(System.out::println);
  }
}

Verarbeitung

Mit forEach kann jedes einzelne Element nacheinander weiterverarbeitet werden.

Verarbeitung - Methoden

void forEach(Consumer<T> consumer)

Verarbeitung - Verwendung

public class Main {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .filter(number -> number > 1)
      .forEach(System.out::println)
 
    Stream.of(1, 2, 3, 4)
      .filter(number -> number > 1)
      .forEach(n -> System.out.println(n));
  }
}

Rest of the Day