Java 8 Streams: Micro Katas

A programming kata is an exercise which helps a programmer hone his skills through practice and repetition.

This article is part of the series Java Tutorial Through Katas.

The article assumes that the reader already has experience with Java, that he is familiar with the basics of unit tests and that he knows how to run them from his favorite IDE (mine is IntelliJ IDEA).

The idea behind exercises displayed below is to learn Java 8 Streaming using test-driven development approach (write the implementation for the first test, confirm that it passes and move to the next).

Each section will start with an objective in form of tests that prove that the implementation will be correct once it’s written. Each of those tests are followed with one possible implementation in Java 7 (or earlier) and Java 8 using Streams. That way reader can compare some of the new features of Java 8 with their equivalents in earlier JDKs. Please try to solve tests without looking at provided solutions.

For more information about TDD best practices, please read the Test Driven Development (TDD): Best Practices Using Java Examples.

Java 8 map

Convert elements of a collection to upper case.

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.ToUpperCase.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Convert elements of a collection to upper case.
*/
public class ToUpperCaseSpec {
@Test
public void transformShouldConvertCollectionElementsToUpperCase() {
List<String> collection = asList("My", "name", "is", "John", "Doe");
List<String> expected = asList("MY", "NAME", "IS", "JOHN", "DOE");
assertThat(transform(collection)).hasSameElementsAs(expected);
}
}

Java 7 (transform7) and Java8 (transform) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;
public class ToUpperCase {
public static List<String> transform7(List<String> collection) {
List<String> coll = new ArrayList<>();
for (String element : collection) {
coll.add(element.toUpperCase());
}
return coll;
}
public static List<String> transform(List<String> collection) {
return collection.stream() // Convert collection to Stream
.map(String::toUpperCase) // Convert each element to upper case
.collect(toList()); // Collect results to a new list
}
}

Java 8 filter

Filter collection so that only elements with less than 4 characters are returned.

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.FilterCollection.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Filter collection so that only elements with less then 4 characters are returned.
*/
public class FilterCollectionSpec {
@Test
public void transformShouldFilterCollection() {
List<String> collection = asList("My", "name", "is", "John", "Doe");
List<String> expected = asList("My", "is", "Doe");
assertThat(transform(collection)).hasSameElementsAs(expected);
}
}

Java 7 (transform7) and Java8 (transform) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;
public class FilterCollection {
public static List<String> transform7(List<String> collection) {
List<String> newCollection = new ArrayList<>();
for (String element : collection) {
if (element.length() < 4) {
newCollection.add(element);
}
}
return newCollection;
}
public static List<String> transform(List<String> collection) {
return collection.stream() // Convert collection to Stream
.filter(value -> value.length() < 4) // Filter elements with length smaller than 4 characters
.collect(toList()); // Collect results to a new list
}
}

Java 8 flatMap

Flatten multidimensional collection

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.FlatCollection.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Flatten multidimensional collection
*/
public class FlatCollectionSpec {
@Test
public void transformShouldFlattenCollection() {
List<List<String>> collection = asList(asList("Viktor", "Farcic"), asList("John", "Doe", "Third"));
List<String> expected = asList("Viktor", "Farcic", "John", "Doe", "Third");
assertThat(transform(collection)).hasSameElementsAs(expected);
}
}

Java 7 (transform7) and Java8 (transform) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;
public class FlatCollection {
public static List<String> transform7(List<List<String>> collection) {
List<String> newCollection = new ArrayList<>();
for (List<String> subCollection : collection) {
for (String value : subCollection) {
newCollection.add(value);
}
}
return newCollection;
}
public static List<String> transform(List<List<String>> collection) {
return collection.stream() // Convert collection to Stream
.flatMap(value -> value.stream()) // Replace list with stream
.collect(toList()); // Collect results to a new list
}
}

Java 8 max and comparator

Get oldest person from the collection

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.OldestPerson.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Get oldest person from the collection
*/
public class OldestPersonSpec {
@Test
public void getOldestPersonShouldReturnOldestPerson() {
Person sara = new Person("Sara", 4);
Person viktor = new Person("Viktor", 40);
Person eva = new Person("Eva", 42);
List<Person> collection = asList(sara, eva, viktor);
assertThat(getOldestPerson(collection)).isEqualToComparingFieldByField(eva);
}
}

Java 7 (getOldestPerson7) and Java8 (getOldestPerson) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.Comparator;
import java.util.List;
public class OldestPerson {
public static Person getOldestPerson7(List<Person> people) {
Person oldestPerson = new Person("", 0);
for (Person person : people) {
if (person.getAge() > oldestPerson.getAge()) {
oldestPerson = person;
}
}
return oldestPerson;
}
public static Person getOldestPerson(List<Person> people) {
return people.stream() // Convert collection to Stream
.max(Comparator.comparing(Person::getAge)) // Compares people ages
.get(); // Gets stream result
}
}

Java 8 sum and reduce

Sum all elements of a collection

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.Sum.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Sum all elements of a collection
*/
public class SumSpec {
@Test
public void transformShouldConvertCollectionElementsToUpperCase() {
List<Integer> numbers = asList(1, 2, 3, 4, 5);
assertThat(calculate(numbers)).isEqualTo(1 + 2 + 3 + 4 + 5);
}
}
view raw SumSpec.java hosted with ❤ by GitHub

Java 7 (calculate7) and Java8 (calculate) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.List;
public class Sum {
public static int calculate7(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
total += number;
}
return total;
}
public static int calculate(List<Integer> people) {
return people.stream() // Convert collection to Stream
.reduce(0, (total, number) -> total + number); // Sum elements with 0 as starting value
}
}
view raw Sum.java hosted with ❤ by GitHub

Java 8 filter and map

Get names of all kids (under age of 18)

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.Kids.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Get names of all kids (under age of 18)
*/
public class KidsSpec {
@Test
public void getKidNameShouldReturnNamesOfAllKidsFromNorway() {
Person sara = new Person("Sara", 4);
Person viktor = new Person("Viktor", 40);
Person eva = new Person("Eva", 42);
Person anna = new Person("Anna", 5);
List<Person> collection = asList(sara, eva, viktor, anna);
assertThat(getKidNames(collection))
.contains("Sara", "Anna")
.doesNotContain("Viktor", "Eva");
}
}
view raw KidsSpec.java hosted with ❤ by GitHub

Java 7 (getKidNames7) and Java8 (getKidNames) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.*;
import static java.util.stream.Collectors.toSet;
public class Kids {
public static Set<String> getKidNames7(List<Person> people) {
Set<String> kids = new HashSet<>();
for (Person person : people) {
if (person.getAge() < 18) {
kids.add(person.getName());
}
}
return kids;
}
public static Set<String> getKidNames(List<Person> people) {
return people.stream()
.filter(person -> person.getAge() < 18) // Filter kids (under age of 18)
.map(Person::getName) // Map Person elements to names
.collect(toSet()); // Collect values to a Set
}
}
view raw Kids.java hosted with ❤ by GitHub

Java 8 summaryStatistics

Get people statistics: average age, count, maximum age, minimum age and sum og all ages.

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.PeopleStats.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Get people statistics: average age, count, maximum age, minimum age and sum og all ages.
*/
public class PeopleStatsSpec {
Person sara = new Person("Sara", 4);
Person viktor = new Person("Viktor", 40);
Person eva = new Person("Eva", 42);
List<Person> collection = asList(sara, eva, viktor);
@Test
public void getStatsShouldReturnAverageAge() {
assertThat(getStats(collection).getAverage())
.isEqualTo((double)(4 + 40 + 42) / 3);
}
@Test
public void getStatsShouldReturnNumberOfPeople() {
assertThat(getStats(collection).getCount())
.isEqualTo(3);
}
@Test
public void getStatsShouldReturnMaximumAge() {
assertThat(getStats(collection).getMax())
.isEqualTo(42);
}
@Test
public void getStatsShouldReturnMinimumAge() {
assertThat(getStats(collection).getMin())
.isEqualTo(4);
}
@Test
public void getStatsShouldReturnSumOfAllAges() {
assertThat(getStats(collection).getSum())
.isEqualTo(40 + 42 + 4);
}
}

Java 7 (getStats7) and Java8 (getStats) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.IntSummaryStatistics;
import java.util.List;
public class PeopleStats {
public static Stats getStats7(List<Person> people) {
long sum = 0;
int min = people.get(0).getAge();
int max = 0;
for (Person person : people) {
int age = person.getAge();
sum += age;
min = Math.min(min, age);
max = Math.max(max, age);
}
return new Stats(people.size(), sum, min, max);
}
public static IntSummaryStatistics getStats(List<Person> people) {
return people.stream()
.mapToInt(Person::getAge)
.summaryStatistics();
}
}

Java 8 partitioningBy

Partition adults and kids

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static com.technologyconversations.java8exercises.streams.Partitioning.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Partition adults and kids
*/
public class PartitioningSpec {
@Test
public void partitionAdultsShouldSeparateKidsFromAdults() {
Person sara = new Person("Sara", 4);
Person viktor = new Person("Viktor", 40);
Person eva = new Person("Eva", 42);
List<Person> collection = asList(sara, eva, viktor);
Map<Boolean, List<Person>> result = partitionAdults(collection);
assertThat(result.get(true)).hasSameElementsAs(asList(viktor, eva));
assertThat(result.get(false)).hasSameElementsAs(asList(sara));
}
}

Java 7 (partitionAdults7) and Java8 (partitionAdults) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.*;
import static java.util.stream.Collectors.*;
public class Partitioning {
public static Map<Boolean, List<Person>> partitionAdults7(List<Person> people) {
Map<Boolean, List<Person>> map = new HashMap<>();
map.put(true, new ArrayList<>());
map.put(false, new ArrayList<>());
for (Person person : people) {
map.get(person.getAge() >= 18).add(person);
}
return map;
}
public static Map<Boolean, List<Person>> partitionAdults(List<Person> people) {
return people.stream() // Convert collection to Stream
.collect(partitioningBy(p -> p.getAge() >= 18)); // Partition stream of people into adults (age => 18) and kids
}
}

Java 8 groupingBy

Group people by nationality

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static com.technologyconversations.java8exercises.streams.Grouping.*;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Group people by nationality
*/
public class GroupingSpec {
@Test
public void partitionAdultsShouldSeparateKidsFromAdults() {
Person sara = new Person("Sara", 4, "Norwegian");
Person viktor = new Person("Viktor", 40, "Serbian");
Person eva = new Person("Eva", 42, "Norwegian");
List<Person> collection = asList(sara, eva, viktor);
Map<String, List<Person>> result = groupByNationality(collection);
assertThat(result.get("Norwegian")).hasSameElementsAs(asList(sara, eva));
assertThat(result.get("Serbian")).hasSameElementsAs(asList(viktor));
}
}

Java 7 (groupByNationality7) and Java8 (groupByNationality) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.*;
public class Grouping {
public static Map<String, List<Person>> groupByNationality7(List<Person> people) {
Map<String, List<Person>> map = new HashMap<>();
for (Person person : people) {
if (!map.containsKey(person.getNationality())) {
map.put(person.getNationality(), new ArrayList<>());
}
map.get(person.getNationality()).add(person);
}
return map;
}
public static Map<String, List<Person>> groupByNationality(List<Person> people) {
return people.stream() // Convert collection to Stream
.collect(groupingBy(Person::getNationality)); // Group people by nationality
}
}
view raw Grouping.java hosted with ❤ by GitHub

Java 8 joining

Return people names separated by comma

Tests

package com.technologyconversations.java8exercises.streams;
import org.junit.Test;
import java.util.List;
import static com.technologyconversations.java8exercises.streams.Joining.namesToString;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
/*
Return people names separated by comma
*/
public class JoiningSpec {
@Test
public void toStringShouldReturnPeopleNamesSeparatedByComma() {
Person sara = new Person("Sara", 4);
Person viktor = new Person("Viktor", 40);
Person eva = new Person("Eva", 42);
List<Person> collection = asList(sara, viktor, eva);
assertThat(namesToString(collection))
.isEqualTo("Names: Sara, Viktor, Eva.");
}
}

Java 7 (namesToString7) and Java8 (namesToString) Implementations

package com.technologyconversations.java8exercises.streams;
import java.util.List;
import static java.util.stream.Collectors.joining;
public class Joining {
public static String namesToString7(List<Person> people) {
String label = "Names: ";
StringBuilder sb = new StringBuilder(label);
for (Person person : people) {
if (sb.length() > label.length()) {
sb.append(", ");
}
sb.append(person.getName());
}
sb.append(".");
return sb.toString();
}
public static String namesToString(List<Person> people) {
return people.stream() // Convert collection to Stream
.map(Person::getName) // Map Person to name
.collect(joining(", ", "Names: ", ".")); // Join names
}
}
view raw Joining.java hosted with ❤ by GitHub

Source

Full source is located in the GitHub repo vfarcic/java-8-exercises. Besides tests and implementation, repository includes build.gradle that can be used, among other things, to download AssertJ dependencies and run tests.

Test-Driven Java Development

7429OS_Test-Driven Java DevelopmentTest-Driven Java Development book wrote by Alex Garcia and me has been published by Packt Publishing. It was a long, demanding, but very rewarding journey that resulted in a very comprehensive hands-on material for all Java developers interested in learning or improving their TDD skills.

If you liked this article I am sure that you’ll find this book very useful. It contains extensive tutorials, guidelines and exercises for all Java developers eager to learn how to successfully apply TDD practices.

You can download a sample or purchase your own copy directly from Packt or Amazon.

Advertisement

7 thoughts on “Java 8 Streams: Micro Katas

  1. Pingback: Java 8 Streams: Micro Katas | Java 8

  2. manuel

    Great post, thanks.
    The test name for “Java 8 sum and reduce” transformShouldConvertCollectionElementsToUpperCase()
    Does not seem the best choice

    Reply
  3. Mahorcu

    Could you provide some info about the library versions needed ?

    I’m using a Maven project in Eclipse, and I’m a little confused connecting the dots from junit to hamcrest (which appears to be needed for that “assertThat(…).hasSameElementsAs(…);” functionality…

    Thanks.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s