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); | |
} | |
} |
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 | |
} | |
} |
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"); | |
} | |
} |
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 | |
} | |
} |
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 | |
} | |
} |
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 | |
} | |
} |
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
Test-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.
Reblogged this on vienergie.
Pingback: Java 8 Streams: Micro Katas | Java 8
Thanks, It is very useful.
An simplification for the sum and reduce kata is
return numbers.stream().reduce(Integer::sum).get();
Great post, thanks.
The test name for “Java 8 sum and reduce” transformShouldConvertCollectionElementsToUpperCase()
Does not seem the best choice
Thanks, really help me out to understand the stream()
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.