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
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
- Sequence of elements - interface to a set of values of specific type computed on demand
- Source - Collections, Array or I/O operations
- Aggregate operations - SQL-like operations: filter, map, reduce etc.
Stream Operations != Collection Operations
- Pipelining
- 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());
- Stream.of(numbers) - creates iterator
- .filter() - new Stream(new ObjFilter(iterator))
- limit - new Stream(new ObjLimit(iterator))
- collect() -> calls 3.next() -> calls 2.next() -> calls 1.next()
- ObjFilter extends Iterator
- 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
Thank You!
Stream API in Android
By Georgi Mirchev
Stream API in Android
- 829