Java 8 Streams

Optional

Traditionally variables can either have a value or be null. Controlling if any variable is null or not is a major problem in Java. Optionals are here to help with this. If it is expected that a value can be null you might better want to use an Optional for it. With an Optional you can not access the value without thinking about the case that it is not there. In this example the parameter maybe is expected to be not filled.

public static int foo(Optional<String> maybe) {

 // 1a
 String result=maybe.map(str -> str+".").orElseGet(() -> getAlternative());

 // 1b orElse() needs to have its parameter evaluated while orElseGet only when its called
 String value=maybe.orElse("Not there!");

 // 2a
 maybe.ifPresent(s -> {
  System.out.println(s);
 });

 // 2b
 maybe.ifPresent(System.out::println);

 // bad example, you can and will forget the if test
 if(maybe.isPresent()) {
   String value=maybe.get();
   System.out.println(value);
 }

}

And here are 3 ways to generate an Optional value. Either the Optional is empty or it has an non null value. The of() method check you not create empty values.

opt1 = Optional.of(objectIsCheckedToBeNotNull);
opt2 = Optional.ofNullable(objectWhichIsAllowedToBeNull);
opt3 = Optional.empty(); // content is null

Interface mit default

Bisher konnten Interfaces nur abstrakte Methoden beinhaltet. Ab jetzt können Interfaces statische Methoden und sogenannte Default Methoden beinhalten. Wenn man die nicht überschreibt, erbt man die samt Funktionalität.

public interface MyInterfaceWithDefaults<T> {

 // abstract method (nothing new)
 public abstract T doSomething();

 // a non abstract method in an interface
 default String getStandardResult() {
  return "New!";
 }

 // a non abstract static method, can not be overwritten in client
 public static String getNoResult() {
  return "New and fixed";
 }

}

FunctionalInterface

Ein sogenanntes FunctionalInterface ist auf den ersten Blick ein normales Interfaces, welches aber genau eine abstrakte Methode hat

@FunctionalInterface // an interface which is only allowed to have one abstract method
public interface MyFunctionalInterface {
 public abstract Integer apply(String parameter);
}

Interessant sind sie, weil man ihnen die neuen Lambda Ausdrücke zuweisen kann.

Man kann sogar Generics dafür benutzen.

@FunctionalInterface
public interface MyFunctionalInterface<V> {
  public abstract V mergeVales(V v1, V v2);
}

Jetzt kann eine Method ein Lamda als Parameter erwarten, dass 2 Werte vom Typ V erwartet und eines vom Typ V zurückliefert:

public static <V> void foo(Set<V> x, MyFunctionalInterface<V> userFunction) {
  userFunction.mergeValues(x.get(0), x.get(1) );
}

Lambda Ausdrücke

The Lambda Expressions

Ein Lambda Ausdruck ist wie eine kompakt zu schreibende Funktion ohne Namen

MyFunctionalInterface howLong=(String yourText) -> yourText.length();

Sogar die Parametertypen sind optional

howLong = (yourText) -> yourText.length();

Eclipse kann beim Speichern automatisch Lambda Ausdrücke im Code ersetzen (Vorsicht, das sind großflächige Änderungen)

Eclipse Preferences, Java Save Action, Lambda

Nachdem der Lambda Ausdruck dem FunctionalInterface zugewiesen wurde, kann man ihn aufrufen, indem man die Methode des Interfaces aufruft

int l=howLong.apply("Hello World");

Es gibt zahlreiche vordefinierte Functional Interfaces

Function<String, Integer> howLong2 = (String yourText) -> yourText.length();

In den Lambda Ausdrücken kann man auch auf den lokelen Scope zugreifen, lokale Variables müssen dabei final sein (oder es zumindest sein können)

Lambda Ausdruck mit zwei Parametern

BiFunction<String, String, Integer> diffInLength = (String s1, String s2) -> s2.length() - s1.length();

Praktische Anwendung, Sortieren von Strings nach Länge

String[] myArrayString={"a","bbb", "cc"};
Arrays.sort(myArrayString,     (String s1, String s2) -> s2.length() - s1.length());

Functional Interface that takes a parameter but returns nothing

Consumer<Integer> consumer = input -> System.err.println(input);

Functional Interface that does not expect a parameter but returns something

Supplier<Integer> supplier = () -> { return 42; };

Function Interface that expects one parameter and returns a boolean

Predicate<String> p=input -> (input.equals("X"))

Warum das Lambda als Comperator akzeptiert wird.

Streams

Weil Streams die Daten erst anziehen, wenn sie auch gebraucht werden, sollte man sie statt Collections verwenden. Z.B. ist es nicht notwendig alle Zahlen in eine Collection schreiben zu lassen, wenn der User am Ende nur die erste Zahl sucht, die größer als 42 ist. Möglicherweise trifft das für die erste Zahl im Stream schon zu, dann wird der Rest erst gar nicht verarbeitet, belegt keinen Speicher, muss nicht aus einer Datenquelle geladen werde, usw. Nachteil am Stream ist allerdings dass sich die Daten in der Quelle noch ändern können. If you consume the stream completely you might be disappointed by the performance loose their overhead brings with them, even if you consume them in parallel.

Create a Stream

Create a stream from a hard codes list of elements

Stream<Integer> myStream = Stream.of(1, 2, 3, 5, 42);

Create a stream from a range of values

Stream<Integer> myStream2 =IntStream.range(1, 42);

Create a stream from an endless working supplier

public class MyEndlessSupplier implements Supplier<Integer> {
        private int currentValue = 0;

        @Override
        public Integer get() {
                return currentValue += 2;
        }
}

Stream.generate(new MyEndlessSupplier()).limit(10);

A more practical example for this (endless stream of random numbers)

Stream.generate(Math::random).limit(10);

If the Stream should end by itself base it on an Iterator / Iterable

public class MyEndingSupplier implements Iterable<Integer> {
        @Override
        public Iterator<Integer> iterator() {
                return new Iterator<Integer>() {
                        private int currentValue = 0;

                        @Override
                        public boolean hasNext() {
                                return currentValue <= 10;
                        }

                        @Override
                        public Integer next() {
                                return currentValue++;
                        }
                };
        }
}

StreamSupport.stream(new MyEndingSupplier().spliterator(), false).forEach(System.err::println);

Create a Stream from the lines of a file

final URL url = getClass().getResource("hotelsWithUmlauts.txt");
Files.lines(Paths.get(url.toURI())).filter(...);

Create a stream out of an Array

Integer[] myArray={1,2,4};
myStream=Arrays.stream(myArray);

From a Collection

List<Integer> myList=...;
myStream=myList.stream();

Ein Stream erzeugen der parallel (mehrere Threads) abgearbeitet wird

myList.parallelStream().forEach(s -> System.out.println(s));

Ein Stream kann dabei ordered oder nicht ordered sein. Hat z.B. auf ein distinct() auf einem parallelen Stream Auswirkungen. Parallelism Sofern einem die Reihenfolge der Streamverarbeitung nicht wichtig ist, erhält man so Parallelverarbeitung die die ursprüngliche Reihenfolge ignoriert.

myStream().unordered().parallel();

Aus mehreren Streams einen Stream machen

String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "e", "f", "g" };
String[] arr3 = { "h", "i", "j" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
Stream<String> stream3 = Stream.of(arr3);

Stream<String> stream = Stream.concat(Stream.concat(stream1, stream2), stream3);

Oder so

Stream<String> stream = Stream.of(stream1, stream2, stream3);

Find Filtering Reordering

Mit

.skip(x)

kann man die ersten x Einträge überspringen.

Filter elements that fulfill condition

myStream().filter(s -> s.length() > 4);
myStream().filter(s -> s!=null);
myStream().filter(Objects::nonNull);
myStream().filter(String.class::isInstance);

Non stream alternative

IterableUtils.find(myCollection, x -> "john".equals(x.getName()));

Is there a value in the Stream that fits, do all fit, did non fit?

boolean e;

e = myStream.anyMatch((i)  -> (i>42) );
e = myStream.allMatch((i)  -> (i>42) );
e = myStream.noneMatch((i) -> (i>42) );

Give me the first element, give me any element

Optional<Person> x=p.stream().findFirst();
Optional<Person> x=p.stream().findAny();

Distinct (each value only once)

myStream.distinct();

Is slower for parallel ordered stream as it might be important which one to keep

Sortieren

Comparator<Person> personByAge = (p1, p2) -> Integer.compare( p1.getAge(), p2.getAge() );
myPeopleStream.sorted(personByAge).forEach(e -> System.out.println(e));

Map values to something else

Jedes Element des Stream an eine Methode zur Verarbeitung geben. Die Methode muss nichts zurückliefern, die Elemente verbleiben im Stream. Die Methode kann aber natürlich die Elemente inhaltlich verändern.

myStream.peek( x-> checkIt(x) );

Einen Wert auf einen anderen abbilden

myStream.map(i -> String.valueOf(i)+", ");

Oder String auf Integer

myPeopleStream.map(Person::getAge);

Wenn man aus einem Wert auch mehrere oder keinen Wert erzeugen können will, dann geht das mit flatMap, welches pro Wert einen neuen Stream hinzufügt. Beispielsweise für jede Zahl im Stream auch ihr doppeltes hinzufügen

myStream.flatMapToInt( x -> IntStream.of(x, x*2));

Class Cast in Stream, first filter on type than cast final Integer i = 42; final Optional<Number> n = Optional.of(i).filter(Number.class::isInstance).map(Number.class::cast);

Consume stream

Count number of values in it

myStream.count();

Durch den Stream durchlaufen

myStream.forEach(System.out::println);

Werte zusammenfassen, Summe aller Lebensjahre, -1 falls Stream leer

myPeopleStream.reduce( -1, (p1, p2) -> p1.getAge()+p2.getAge());

Höchstes Alter

myPeopleStream.reduce(-1, Math::max);

If the reduced value is of a different type than the values we reduce we need to explain how to create the reduced value, how to add another value to it and how to reduce to values into one

myPeopleStream.reduce(
 // identity value
 new MyContainer(),
 // accumulator, called all the time
 (col, data) -> col.add(data),
 // combiner, called to collect values of parallel executions
 (col1, col2) -> col1.addAll(col2)
);

Mit collect kann man einen Stream beenden und die Resultate zurückliefern. collect erwartet als Parameter die Information wie die Daten aufgesammelt werden sollen.

Foo result=myStream.collect(Foo::new, Foo::accept, Foo::combine);

Der erste Parameter erzeugt eine neue Instanz des Sammlers (hier ein neues Foo Objekt mit dem Default Konstruktor). Der zweite Parameter wird mit jedem neuen Objekt aus dem Stream aufgerufen. Foo kann dann irgendwas mit dem Objekt machen. Der dritte Parameter wird mit einem anderen Objekt aus dem Stream aufgerufen und wir müssen seinen Inhalt mit uns verschmelzen. Am Ende liefern wir genau ein Foo Objekt zurück, das alle anderen in sich vereinigt hat.

Collectors hat eine Reihe nützlicher Funktionen. Aus dem Stream eine Liste erzeugen

List<Person> result=myPeople.collect( Collectors.toList() );

Aus dem Stream eine Map erzeugen, Geschlecht -> alle Namen

Map<Character, String> res=p.stream().collect( Collectors.toMap(x -> x.getSex(), x -> x.getName()));

Durchschnittlichen Wert aus Stream ermitteln

Double averageAge = myPeople.collect( Collectors.averagingInt(p -> p.getAge() ));

Den höchsten Wert mithilfe eines anzugebenden Comperators finden

p.stream().collect( Collectors.maxBy( (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()) ));

Statistiken erhalten

IntSummaryStatistics stats = myPeople.collect( Collectors.summarizingInt(p -> p.getAge() ) );

Elemente gruppieren, hier alle Personen nach Geschlecht gruppieren

Map<Character, List<Person> > res=p.stream().collect( Collectors.groupingBy(Person::getSex ));

Element aufteilen, in alle die, die eine Bedingung erfüllen, und alle die sie nicht erfüllen. Z.B. ist Person über 30 oder nicht

Map<Boolean, List<Person>> r=p.stream().collect( Collectors.partitioningBy(x -> (x.getAge()>30)  ));