JVM Advent

The JVM Programming Advent Calendar

19 Lessons in a Kata

We learn best by doing

In this blog, I will show you how to get started coding the Eclipse Collections Pet kata. If you want to become proficient in Eclipse Collections, you should practice the kata on your own. In order to truly master the kata, try teaching the kata to others. Teaching results in deeper learning.

Follow the leader

There are slides online that I will walk through, as well as links to the code in GitHub. I will complete the first exercise of the Pet Kata.  There are 4 failing tests in which I will demonstrate 19 different solutions.  Follow along in your Java IDE.  Try the different solutions that I share below.

Step 1 – Download Eclipse Collections kata

  • Clone the repo here— https://github.com/eclipse/eclipse-collections-kata
  • Import the pom.xml file located in the root as a Maven Project in your Java IDE
  • Select the Pet Kata folder and go to the tests directory
  • Start with Exercise 1
  • Run the unit tests — They should fail!
  • Get the tests to pass one by one
  • When you complete the last exercise— Congratulations!
  • Star our repo if you enjoy the kata

The Slides

The online slides for the tutorial portion of the Pet Kata can be found here.

Understanding the Pet KATA domain

Person, Pet and PetType are the domain classes in the kata. A Person can have zero or more pets. Each Pet has exactly one PetType. PetType is an enum. See the domain model for more details.

Exercise 1 – Lessons

Collect Pattern

The collect pattern is used to transform one collection type to another. This pattern is also known as map or transform.

Select Pattern

The select pattern is used to filter a collection on a positive condition. This pattern is also known as filter.

Exercise 1 – Fix The FAILING tests
Test #1 – Get First Names of People
@Test
public void getFirstNamesOfAllPeople()
{
    // Replace null, with a transformation method on MutableList.
    MutableList<String> firstNames = null; // this.people...

    MutableList<String> expectedFirstNames = 
            Lists.mutable.with("Mary", "Bob", "Ted", "Jake", "Barry", "Terry", "Harry", "John");
    Assert.assertEquals(expectedFirstNames, firstNames);
}

In the first test, we need to write the code to collect the names of the people contained in “this.people”. The names returned should match the value of expectedFirstNames. The collect method takes a Function as a parameter.  I will replace the null value for the firstNames variable with different solutions.

Solution (1) – Use a Lambda with collect

MutableList<String> firstNames = 
    this.people.collect(person -> person.getFirstName());

Solution (2) – Use a Method Reference with collect

MutableList<String> firstNames = 
    this.people.collect(Person::getFirstName);

I prefer using method references whenever possible. Method references are more succinct, and are often self-documenting.

Solution (3) – Use a Java Stream with Collectors2

MutableList<String> firstNames =
    this.people.stream()
        .map(Person::getFirstName)
        .collect(Collectors2.toList());

Eclipse Collections has a static utility class named Collectors2.  There are Collector implementations on this class that return Eclipse Collections types.

Solution (4) – Use an Anonymous Inner Class with collect

MutableList<String> firstNames =
    this.people.collect(new Function<Person, String>()
    {
        @Override
        public String valueOf(Person person)
        {
            return person.getFirstName();
        }
    });

I would not usually write code like this in Java 8 or above. However, it is useful sometimes if you want to see all of the types that are involved. Most noteworthy, this code will still work in any version of Java after Java 5.

Test #2 – Get Names of Mary Smith’s Pets
@Test
public void getNamesOfMarySmithsPets()
{
    Person person = this.getPersonNamed("Mary Smith");
    MutableList<Pet> pets = person.getPets();

    // Replace null, with a transformation method on MutableList.
    MutableList<String> names = null; // pets...

    Assert.assertEquals("Tabby", names.makeString());
}

In the second test, we want to collect the names of “Mary Smith’s” pets. Mary Smith has one pet named “Tabby”. Repetition is good for learning.  We will use the collect method again to obtain the names of “Mary Smith’s” pets.

Solution (5) – Use a Lambda with collect

MutableList<String> names = pets.collect(pet -> pet.getName());

Solution (6) – Use a Method Reference with collect

MutableList<String> names = pets.collect(Pet::getName);

Solution (7) – Use a Java Stream with Collectors2

MutableList<String> names = pets.stream()
    .map(Pet::getName)
    .collect(Collectors2.toList());

Solution (8) – Use an Anonymous Inner Class with collect

MutableList<String> names = 
    pets.collect(new Function<Pet, String>()
        {
            @Override
            public String valueOf(Pet pet)
            {
                return pet.getName();
            }
        });
Test #3 – Get People with Cats
@Test
public void getPeopleWithCats()
{
    // Replace null, with a positive filtering method on MutableList.
    MutableList<Person> peopleWithCats = null;  // this.people...

    Verify.assertSize(2, peopleWithCats);
}

In the third test, we want to find all of the people who have cats. As a result, we will use the select method. The select method takes a Predicate as a parameter.

Solution (9) – Use a Lambda with select

MutableList<Person> peopleWithCats =
    this.people.select(person -> person.hasPet(PetType.CAT));

Solution (10) – Use a Method Reference with selectWith

MutableList<Person> peopleWithCats =
    this.people.selectWith(Person::hasPet, PetType.CAT);

We cannot use a method reference with the select method in this example.  We can use the method selectWith, which takes a Predicate2 and a parameter.

Solution (11) – Use a Java Stream with Collectors2

MutableList<Person> peopleWithCats =
    this.people.stream()
        .filter(person -> person.hasPet(PetType.CAT))
        .collect(Collectors2.toList());

We cannot use a method reference with the filter method in this case.

Solution (12) – Use a Java Stream with Collectors2.selectWith()

MutableList<Person> peopleWithCats =
    this.people.stream()
        .collect(Collectors2.selectWith(
            Person::hasPet,
            PetType.CAT,
            Lists.mutable::empty));

We can use a method reference with the selectWith method on Collectors2. The selectWith method takes three parameters – a Predicate2, a parameter to pass to the Predicate2 and finally, a Supplier.

Solution (13) – Use reduceInPlace with Collectors.selectWith()

MutableList<Person> peopleWithCats =
    this.people.reduceInPlace(Collectors2.selectWith(
                Person::hasPet,
                PetType.CAT,
                Lists.mutable::empty));

We can reduce the boilerplate of stream().collect() by just calling reduceInPlace().

Solution (14) – Use an Anonymous Inner Class with select

MutableList<Person> peopleWithCats =
    this.people.select(new Predicate<Person>() 
    {
        @Override
        public boolean accept(Person person)
        {
            return person.hasPet(PetType.CAT);
        }
    });

Solution (15) – Use an Anonymous Inner Class with selectWith

MutableList<Person> peopleWithCats =
    this.people.selectWith(new Predicate2<Person, PetType>() 
    {
        @Override
        public boolean accept(Person person, PetType petType)
        {
            return person.hasPet(petType);
        }
    }, PetType.CAT);
Test #4 – Get People without Cats
@Test
public void getPeopleWithoutCats()
{
    // Replace null, with a negative filtering method on MutableList.
    MutableList<Person> peopleWithoutCats = null;  // this.people...

    Verify.assertSize(6, peopleWithoutCats);
}

In final test, we want to find all of the people who do not have cats. There is a natural opposite to select available in Eclipse Collections. The method named reject, filters based on the negative result of applying a Predicate.

Solution (16) – Use a Lambda with reject

MutableList<Person> peopleWithoutCats =
    this.people.reject(person -> person.hasPet(PetType.CAT));

Solution (17) – Use a Method Reference with rejectWith

MutableList<Person> peopleWithoutCats =
    this.people.rejectWith(Person::hasPet, PetType.CAT);

Eclipse Collections has good symmetry in its API.  As a result, since there is a selectWith method, there is a rejectWith counterpart.

Solution (18) – Use a Java Stream with Collectors2

MutableList<Person> peopleWithoutCats =
    this.people.stream()
        .collect(Collectors2.rejectWith(
            Person::hasPet,
            PetType.CAT,
            Lists.mutable::empty));

There is no natural opposite of filter in Java Streams (filter IN vs. filter OUT).  However, we can use rejectWith method available on Collectors2 with a method reference.

Solution (19) – Use an Anonymous Inner Class with reject

MutableList<Person> peopleWithoutCats =
    this.people.reject(new Predicate<Person>() 
    {
        @Override
        public boolean accept(Person person)
        {
            return person.hasPet(PetType.CAT);
        }
    });

For further study

Try solving the Eclipse Collections Pet kata on your own. Use method references and lambdas. Use Java Streams with and without Collectors2. Experiment with primitive collections in the later exercises. See how different JVM language like Groovy, Kotlin or Scala can solve the kata. There is an amazing amount of potential for learning just on the first exercise alone.

Footnote

Eclipse Collections is open for contributions. You can also contribute to the kata. If you like the library, you can let us know by starring it on GitHub.

Author: donraab

Java Champion. Creator of the Eclipse Collections Java library (http://www.eclipse.org/collections/). Inspired by Smalltalk. Opinions are my own.

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2019 JVM Advent

Theme by Anders Norén

%d bloggers like this: