Skip to the content.

#Streams

-

What we’ll cover

-

What is a Stream?

-

Streams, visualized:

-

Usage

// This method definition showcases for-each-loop syntax
public void forloopPrint(String[] stringArray) {
    for (String currentString : stringArray) {
        System.out.println(currentString);
    }
}

// This method definition showcases Stream.forEach syntax
public void streamPrint(String[] stringArray) {
    Stream<String> stringStream = Stream.of(stringArray);
    stringStream.forEach(System.out::print);
}

-

Stream Creation

-

From Array, strategy 1

/** @param stringArray source array to create stream
 *  @return stream representation of this array */
public Stream<String> fromArray1(String[] stringArray) {
    Stream<String> stringStream = Arrays.stream(stringArray);
    return stringStream;
}

-

From Array, strategy 2

/** @param stringArray source array to create stream
 *  @return stream representation of this array */
public Stream<String> fromArray2(String[] stringArray) {
    Stream<String> stringStream = Stream.of(stringArray);
    return stringStream;
}

Two ways of getting the same result, and one should not be surprised to find this from time to time in the Java Standard Libraries.

-

From varargs

/** @return stream representation of this array */
public Stream<String> fromVarargs() {
    Stream<String> stringStream = Stream.of("The", "Quick", "Brown", "Fox", "Jumps");
    return stringStream;
}

-

From List

/** @param stringList source list to create stream
*   @return stream representation of this List */
public Stream<String> fromList(List<String> stringList) {
    Stream<String> stringStream = stringList.stream();
    return stringStream;
}

-

.generate, strategy 1

/** @return endless stream */
public Stream<String> fromGenerator1() {
    Stream<String> stringStream = Stream.generate(() -> "Hello World");
    return stringStream;
}

-

.generate, strategy 2

/** @return endless stream */
public Stream<Double> fromGenerator2() {
    Stream<Double> randoms = Stream.generate(Math::random);
    return randoms;
}

-

.empty

public Stream<String> fromEmpty() {
	return Stream.empty();
}

-

.iterate

public Stream<Integer> fromIterator() {
        return Stream.iterate(0, n -> n + 1).limit(10);
}

-

Extracting substreams

-

Extracting substreams

public Stream<String> getSubStream(String[] stringArray, int startIndex, int endIndex) {
	return Arrays.stream(stringArray, startIndex, endIndex);
}

public Stream<String> getSubStream(String[] stringArray, int endIndex) {
	return Arrays.stream(stringArray).limit(endIndex);
}

-

Combining substreams

-

Stream.concat

public Stream<String> combineStreams(String[] array1, String[] array2) {
    Stream<String> stream1 = Arrays.stream(array1);
    Stream<String> stream2 = Arrays.stream(array2);

    return Stream.concat(stream1, stream2);
}

-

Transformations

-

Filter, Map, and FlatMap

-

Filter

public Stream<String> getStringsLongerThan(String[] stringArray, int length) {
    return Arrays.stream(stringArray)
            .filter(word -> word.length() > length);
}

public Stream<String>getStringsShorterThan(String[] stringArray, int length) {
    return Arrays.stream(stringArray)
            .filter(word -> word.length() < length);
}

-

Map

public Stream<String> letters(String someWord) {
    String[] characters = someWord.split("");
    return Stream.of(characters);
}

public Stream<Stream<String>> wordsMap(String[] someWords) {
    return Stream.of(someWords).map(w -> letters(w));
}

-

FlatMap

public Stream<String> letters(String someWord) {
    String[] characters = someWord.split("");
    return Stream.of(characters);
}

public Stream<String> wordsFlatMap(String[] stringArray) {
    Stream<String> wordStream = Stream.of(stringArray);
    List<String> wordList = wordStream.collect(Collectors.toList());
    return wordList.stream().flatMap(w -> letters(w));
}

-

Distinct

public Stream<String> uniqueWords(String... words) {
	return Stream.of(words).distinct();
}

-

Transformations: Sorted

public Stream<String> sort(String[] words) {
    return Arrays.stream(words).sorted();
}

-

Simple Reductions

-

Optional<T>

-

.count

/** @return number of elements in an array using a stream */
public int getCount(String[] stringArray) {
    return (int) Arrays.stream(stringArray).count();
}

-

.min, .max

/** @return shortest String object in an array using a stream */
public Optional<String> getMin(String[] stringArray) {
    return Arrays.stream(stringArray).min(String::compareToIgnoreCase);
}

/** @return longest String object in an array using a stream */
public Optional<String> getMax(String[] stringArray) {
    return Arrays.stream(stringArray).max(String::compareToIgnoreCase);
}

-

.findFirst, .findAny

/** @return get first String from an array using a stream */
public Optional<String> getFirst(String[] stringArray) {
    return Arrays.stream(stringArray).findFirst();
}

/** @return a random string in an array using a stream */
public Optional<String> getRandom(String[] stringArray) {
    return Arrays.stream(stringArray).findAny();
}

-

More Streams…

  1. Optional Type
  2. Optional Type Usage
  3. Optional Type Misuse

-

Optional Type Creation

-

.of, .empty

public static Optional<Double> inverse(Double x) {
	return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

-

.ofNullable

/** return Optional.of(obj) if obj is not null, else
  * return Optional.empty() otherwise. */
public static Optional<String> demoOfNullable(String arg) {
	return Optional.ofNullable(arg);
}

-

Composing Optional Value Functions
With .flatMap

-

Chaining Method Calls

-

Avoiding Optional usage puts code
at risk of NullPointerException

S s = new S();
U u = s.f().g();

-

.map yields (potentially) nested structure

S s = new S();
Optional<Optional<U>> = s.f().map(T::g);

-

.flatMap yields flat structure

S s = new S();
Optional<U> = s.f().flatMap(T::g);

-

Collecting Results

-

Relevant Functional Interfaces

| Name | Returns | Takes Argument 1 | Takes Argument 2 | |————|———|——————|——————| | Runnable | No | No | No | | Supplier | Yes | No | No | | Consumer | No | Yes | No | | BiConsumer | No | Yes | Yes | | Function | Yes | Yes | No | | BiFunction | Yes | Yes | Yes |

-

Relevant Functional Interfaces

-

Relevant Jargon

-

Method References ::

Consumer<String> stringConsumer = System.out::print;
List<String> stringList = ...
Stream<String> s = stringList.stream();
s.forEach(stringConsumer);

-

More Method References ::

class SquareMaker {
     public double square(double num){
        return Math.pow(num , 2);
    }
}

class DemoSquareMaker {
	public static void main(String[] args) {
		SquareMaker squareMaker = new SquareMaker();
		Function<Double, Double> squareMethod = squareMaker::square;
		double ans = squareMethod.apply(23.0);
	}

}

-

Using .toArray() to collect results

public class CollectorsDemo {
    private final List<String> list;
    public CollectorsDemo(List<String> list) {
        this.list = list;
    }

    private Stream<String> toStream() {
        return list.stream();
    }

    public String[] toArray() {
    	return toStream().toArray(String[]::new);
    }
}

-

Using .collect()
to collect to a List

public class CollectorsDemo {
    private final List<String> list;
    public CollectorsDemo(List<String> list) {
        this.list = list;
    }

    private Stream<String> toStream() {
        return list.stream();
    }

    public List<String> toList() {
        return toStream().collect(Collectors.toList());
    }
}

-

Using .collect()
to collect to a Set

public class CollectorsDemo {
    private final List<String> list;
    public CollectorsDemo(List<String> list) {
        this.list = list;
    }

    private Stream<String> toStream() {
        return list.stream();
    }

    public Set<String> toSet() {
        return toStream().collect(Collectors.toSet());
    }
}

-

Collecting into Maps

-

Using .collect()
to collect to a Map

public class CollectorsDemo {
    private final List<String> list;
    public CollectorsDemo(List<String> list) {
        this.list = list;
    }

    private Stream<String> toStream() {
        return list.stream();
    }

    public Map<Integer, String> toMap() {
        return toStream().collect(
        	Collectors.toMap(String::hashCode, String::toString));
    }
}

-

Using .collect()
to collect to a Map

public class CollectorsDemo {
    private final List<String> list;
    public CollectorsDemo(List<String> list) {
        this.list = list;
    }

    private Stream<String> toStream() {
        return list.stream();
    }

    public Map<Integer, String> toMap() {
        return toStream().collect(
        	Collectors.toMap(String::hashCode, Function::identity));
    }
}

-

Grouping and Partitioning

-

.groupingBy()

public Map<String, List<Locale>> groupingByDemo() {
    Stream<Locale> locales = LocaleFactory.createLocaleStream(999);
    return locales.collect(Collectors.groupingBy(Locale::getCountry));
}

- ##.partitioningBy()

-

Downstream Collectors

class Demo {
	public Map<String, Set<Locale>> demoDownstreamCollectors1() {
	    Stream<Locale> locales = LocaleFactory.createLocaleStream();
	    Map<String, Set<Locale>> countryToLocaleSet = locales.collect(
	            groupingBy(Locale::getCountry, toSet()));

	    return countryToLocaleSet;
	}
}

-

Downstream Collectors (continued)

class Demo {
    public Map<String, Long> demoDownstreamCollectors2() {
        Stream<Locale> locales = LocaleFactory.createLocaleStream();
        Map<String, Long> countryToLocaleSet = locales.collect(
                groupingBy(Locale::getCountry, counting()));

        return countryToLocaleSet;
    }
}

-

Reduction Operations

class Demo {
IntegerFactory integerFactory = new IntegerFactory(0, 999);
    public void demo() {
        List<Integer> values = integerFactory.createList(100);
        Optional<Integer> sum = values.stream().reduce((x, y) -> x + y);
    }
}

If is some reduction operation.
Then, the reduction yield is
v0 • v1 • v2 …,
where vi • vi+1
represents the function call •(vi, vi+1)

-

Primitive Type Streams

-

Populate IntStream

class PrimitiveStreams {
    public IntStream demoOf() {
        IntStream intStream = IntStream.of(1, 3, 5, 8, 13);
        return intStream;
    }
}

-

Generate IntStream

class PrimitiveStreams {
    public IntStream demoGenerate() {
        IntegerFactory integerFactory = new IntegerFactory(0,999);
        IntStream intStream = IntStream.generate(integerFactory::createInteger);
        return intStream;
    }
}

-

Create exclusive range IntStream

class PrimitiveStreams {
    /** upper bound is excluded
     *  @param min value to generate
     *  @param max value to generate
     *  @return range of numbers betwen min and max */
    public IntStream demoRange(int min, int max) {
        return IntStream.range(min, max);
    }
}

-

Create inclusive range IntStream

class PrimitiveStreams {
    /** upper bound is included
     *  @param min value to generate
     *  @param max value to generate
     *  @return range of numbers betwen min and max */
    public IntStream demoRange(int min, int max) {
        return IntStream.range(min, max);
    }
}

-

Converting from Primitive to Object stream

public class PrimitiveStreams {
    public Stream<Integer> demoBoxStream() {
        return IntStream.of(0, 1, 2).boxed();
    }
}

-

Parallel Streams

-

Improper usage

class Demo {
	public void demo() {
		int[] shortWords = new int[12];
		words.parallelStream().forEach(
			s -> { if(s.length <12) shorts[s.length()]++; });
			// Error - race condition!
		System.out.println(Arrays.toString(shortWords));
	}
}

-

Proper usage

class Demo {
  public Map<Integer, Long> wordCountMap(List<String> words) {
    Map<Integer, Long> shortWordCounts = words.parallelStream()
      .filter(s -> s.length() < 10)
      .collect(Collectors.groupingBy(String::length, Collectors.counting()));
    return shortWordCounts;
}

-