Stream API in Android

Stream API: The Idea

// MAX(*) is important here
SELECT id, MAX(value) from transactions

Reuse the same query for collections

No need to recreate MAX function with for loops

Leverage multicore / parallel code

Java 8 Example

// Filter by type
List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}

// Sort
Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});

// Select the ID only
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

For loops

List<Integer> transactionsIds =
    // Set of elements
    transactions.stream()
                 // Intermediate operations
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                // Terminal operation, which executes the stream
                .collect(toList());

Stream API

Stream Definition

  1. Sequence of elements - interface to a set of values of specific type computed on demand
     
  2. Source - Collections, Array or I/O operations
     
  3. Aggregate operations - SQL-like operations: filter, map, reduce etc.

Stream Operations != Collection Operations

  1. Pipelining 
  2. Internal iteration - iterates behind the scenes

Streams on Android

Library

list.stream() -> Stream.of(list)

What Android is missing

.parallelStream()
List<Integer> transactionsIds = 
    // no more .stream()
    transactions.parallelStream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Short-circuiting terminal operations (1)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> twoEvenSquares = 
    numbers.stream()
           .filter(n -> {
                    System.out.println("filtering " + n); 
                    return n % 2 == 0;
                  })
           .map(n -> {
                    System.out.println("mapping " + n);
                    return n * n;
                  })
           // Short-circuiting operation
           .limit(2)
           .collect(toList());

// Result
filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4

Short-circuiting terminal operations (2)

  • findFirst / findLast
  • single / findSingle
  • anyMatch
  • allMatch
  • findIndexed
  • limit
  • ...

com.annimon.stream.Stream.java -> Ctrl + F -> "short-circuiting"

For Android

How do operators work?

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> eventNumbers = Stream.of(numbers)
                .filter(n -> {
                        System.out.println("filtering " + n);
                        return n % 2 == 0;
                 })
                .limit(2)
                .collect(Collectors.<Integer>toList());
  1. Stream.of(numbers) - creates iterator
  2. .filter() - new Stream(new ObjFilter(iterator))
  3. limit - new Stream(new ObjLimit(iterator))
  4. collect() -> calls 3.next() -> calls 2.next() -> calls 1.next()
  1. ObjFilter extends Iterator
  2. ObjLimit extends Iterator and adds maxSize state

Numeric Streams

  • mapToInt
  • mapToDouble
  • mapToLong
  • flatMapToInt
  • flatMapToDouble
  • flatMapToLong

Operators:

  • sum()
  • min()
  • max()
  • count()
  • boxed()

Classes:

  • IntStream
  • DoubleStream
  • LongStream

Examples - Stream.

 

  • of(Iterable) ...
  • ofNullable(Iterable) ...
  • range(from, to) ...
  • zip(stream1, stream2, function) - combines
  • merge(stream1, stream2, function) - picks values
  • generate(Supplier) - generate custom values

Examples - Operators (1)

List<String> words = new ArrayList() {{
  add("Word");
  add("Paper");
  add("Word");
  add("Android Rulez");
  add("Device Information");
}};
Long distinctWords = 
  Stream.of(words)
    .distinct()
    .collect(Collectors.counting());

distinct

Long oneWordCount = 
  Stream.of(words)
    .filter(p -> p.split(" ").length == 1)
    .collect(Collectors.counting());

filter

String indexedSequence = 
  Stream.of(words)
    .indexed()
    .map(x -> String.format("%s %s ", x.getFirst(), x.getSecond()))
    .collect(Collectors.counting());
// 0 Word 1 Paper 2 Word 3 Android Rulez 4 Device Information 

indexed

Example - Operators (2)

Stream.of(words)
  .sortBy(String::length)
  .collect(Collectors.toList())

Sorting

Stream.of(words)
  .sorted(Comparator)
  .collect(Collectors.toList())

takeWhile / dropWhile / chunkBy

Stream.of("a", "b", "cd", "ef", "g")
    .takeWhile(s -> s.length() == 1) // [a, b]

Stream.of("a", "b", "cd", "ef", "g")
    .dropWhile(s -> s.length() == 1) // [cd, ef, g]
Stream.of("a", "b", "cd", "ef")
    .chunkBy(String::length) // [[a, b], [cd, ef]]

Example - Operators (3)

 filterNot / withoutNulls

map-filter-takeWhile-forEach*Indexed

Stream.of("a", "b", "c")
    .mapIndexed((i, s) -> s + Integer.toString(i)) // [a0, b1, c2]
stream.filterNot(String::isEmpty)

Stream.of("a", null, "c", "d", null)
    .withoutNulls() // [a, c, d]

average / sum

stream
  .mapToDouble(w -> w.getTranslate().length())
  .average() // only in mapToDouble

stream
  .mapToInt(w -> w.getTranslate().length())
  .sum()

Custom Operator

.custom()

Collectors (1)

Collectors (2)

You can use Collectors to transform data into different types of structures like from List to Map etc.

TreeMap<String, List<PaymentResponse>> map = 
  Stream.of(sortedPayments)
    .collect(
             Collectors.groupingBy(PaymentResponse::getAccountId, 
                                   TreeMap::new, 
                                   Collectors.toList())
    );

Custom Implementation

Why Streams?

  • Declarative way of programming
  • Maintainable queries - you just enter the stream and add a new operator
  • Easier way of converting data structures using collectors
  • Helps escaping nested for loops
  • Prevents ConcurrentModificationException

+

-

  • Not as good performance vs loops - 0.546590 sec. vs 0.041856 sec for 10000000 items. (filter -> map -> collect)
  • May be an overkill for smaller tasks
  • Readability is subjective

Useful Information

  • Processing Data with Java SE 8 Streams, Part 1 - LINK
  • Part 2: Processing Data with Java SE 8 Streams - LINK
  • Lightweight Stream API - LINK
  • Lightweight Stream API Android Example - LINK
  • IBM Introduction to stream library - LINK

Thank You!

Stream API in Android

By Georgi Mirchev

Stream API in Android

  • 829