Vereiesten: Om dit concept te begrijpen dien je comfortabel te zijn met interfaces in Java. Verder verwachten we ook een basis begrip van lambda expressies
Voor de voorbeelden in dit theorie gedeelte gebruiken we de code van de IndianDishQuiz
opdracht.
Specifiek een IndianDish
.
De data kan gevonden worden in de indian_food.csv. Let op dat in deze versie de ingredienten ook gesplitst zijn in een lijst van strings. (in plaats van alleen een komma gescheiden string)
Na het lezen van de theorie zou je in staat moeten zijn om het volgende code fragment te begrijpen:
// Using a list of integers.
List<Integer> list = Arrays.asList(8, 4, 5, 7, 2);
int totalTimes2 =
// Take the list.
list
// Use it as a stream.
.stream()
// Convert (map) each integer using this calculation (lambda expression)
.map(x -> x * 2)
// Add the resulting integers together (another lambda expression)
.reduce(Integer::sum);
// Answer should be (8*2 + 4*2 + 5*2 + 7*2 + 2*2 =) 52
System.out.println(totalTimes2);
Na bijna een jaar programmeren is het je waarschijnlijk opgevallen dat veel software vooral vertrouwd op het opslaan en verwerken van collecties van data. Alles wat we gaan laten zien kan gedaan worden met kennis van Java die je al hebt.
Er is echter een probleem: Hoewel JIJ weet hoe je dit kunt doen in Java, het uitdrukken van overeenkomende ideeen achter je code kan lastig zijn. Andere collega’s kunnen makkelijker communiceren met database termen.
Bijvoorbeeld: “Van de lijst van dishes wil alleen degenen zien die vegetarisch zijn.”
SELECT * FROM dishes WHERE diet='vegetarian'
De eenvoudige concept kost, met alleen collections, meer stappen om voor elkaar te krijgen:
ArrayList<Dish> result = new ArrayList<Dish>();
for (Dish dish:dishes) {
if (dish.getDiet().equals("vegetarian")) {
result.add(dish);
}
}
return result;
Streams zijn een update in Java waarmee lijsten en collecties worden aangeboden als een reeks van elementen. Deze streams voorzien ook in een set van standaard operaties om deze elementen te filteren, sorteren of updaten. Voorheen moest iedere ontwikkelaar hun eigen code schrijven om dat te doen.
Let op: Zoals eerder aangegeven, dit kan nog steeds. Maar wanneer je streams begrijpt, zul je doorhebben dat je code meer op een query lijkt welke eenvoudig aangepast kan worden bij een update.
Met streams willen we de namen laten zien van dishes met minder dan 400 calorieen gesorteerd op calorieen:
// We're using a list of menu items.
List<MenuItem> menu = readMenu();
List<String> lowCaloricDishesName =
// Using the menu collection.
menu
// Start by providing the stream source.
.stream()
// Only select dishes WHERE calories < 400
.filter(d -> d.getCalories() < 400)
// Order using those calories
.sorted(comparing(Dish::getCalories))
// Select the name of the dish.
.map(Dish::getName)
// Add the result to a list again.
.collect(toList());
In this code .filter
, .sort
, .map
and .collect
are standard operations
on a stream that allow you to define the steps that manipulate the original content
and result in the answer you want.
The code contains three parts:
list.stream()
.filter
, .limit
, etc..collect
in this case.The rest of the theory will show you the most common operations on a stream.
One side node: In this code the intermediate opreations are chained together: stream.doSomething().doAnotherThing().etc()
This is called creating a pipeline of operations. Each operation gives you another stream
that contains slightly different data along the way.
This may cause very long lines of code, so we split them on the dots.
Er zijn verschillende manieren om een stream
op te zetten, de eerste twee worden het meest gebruikt.
Integer[] arr = {1,2,3};
Stream<Integer> stream = Arrays.stream(arr);
Collection<Integer> = new ArrayList<>();
Stream<Integer> stream = collection.stream();
Stream.of(...)
:Stream<Integer> stream = Stream.of(1,2,3,4,5);
Stream<Integer> stream = Stream.iterate(0, n->n+1).limit(10);
(Take your time to wrap your head around that last one: Create a sequence starting at 0. Increase each next value. (In this case by 1) Please stop after 10 elements.)
Na het aanroepen van deze methoden wordt je een nieuwe (weergave van) de stream aangeboden die je verder kunt manipuleren.
.filter(x -> x > 5)
.filter(x -> x.equals(">")
.map(x -> x - 1)`
.map(dish -> dish->getCalories())`
.limit(10)
.sorted(comparing(Dish::getCalories))
These methods take a stream and convert to a single result. (This result is not a stream otherwise it would have been an intermediate method.)
.count()
.reduce(Integer::sum)
.distince()
.collect(Collectors.toList())
Streams as defined so far seem to require reference types as their
elements. For instance Stream<String>
or Stream<Integer>
.
To use with primitive types there are specialised implementations:
To convert any stream to a specific primitive stream a special conversion is required:
This allows dedicated terminal operation:
These are the basic aggregation function we know (and love) from databases
except for .count()
because that one doesn’t require a primitive type stream.
Collection<Dish> yourOrder = getOrder();
int totalCalories = yourOrder
.stream()
.mapToInt(dish -> dish.getCalories())
.sum();
System.out.println("Total calories in your order : "+totalCalories);
As stated before the Dish
class itself contains a list of ingredients
used in that dish. Let’s say that we have an order for a certain set of
dishes. How would you get a list of all the required ingredients.
Effectively this is an array within an array: (Please forgive the JSON notation.)
[
{
"name" : "Dish 1",
"ingredients" : [ "apples", "cumin", "rice" ]
},
{
"name" : "Dish 2",
"ingredients" : [ "rice", "sugar", "ghee", "cinnamon" ]
},
{
// etc.
}
]
I would like to get a list that contains only:
["apples", "cumin", "rice", "sugar", "ghee", "cinnamon"]
You can solve this problem using the .flatMap
intermediate operation:
List<Dish> order = getOrderList();
List<String> uniqueIngredients =
order
.stream()
// Temporarily create a stream that only contains arraylists.
.map(d -> d.getIngredients())
// Merge those separate arraylists into a single stream.
.flatMap(ArrayList::stream)
// Filter out duplicates.
.distinct()
.collect(Collectors.toList());