Предлог By и его положительный вклад в продуктивность.

Работайте с умом, используя намерения, раскрывающие API Java

У всех нас ограниченное время. Все, что экономит время на кодирование и улучшает читаемость нашего кода, прекрасно. Читаемость является ключевым моментом, поскольку мы читаем код гораздо больше, чем пишем его. Многим людям, возможно, придется читать фрагмент кода в течение его срока службы, поэтому экономия времени - настоящий бонус.

Чем можно сэкономить время с коллекциями Eclipse?

Вы можете aggregate, group, sum, count, min, max и преобразовать в другие отсортированные коллекции по применению некоторого Function. Следующие By методы доступны для каждого RichIterable подтипа в Коллекциях Eclipse.

Я покажу три примера из Eclipse Collections Pet Kata, которые иллюстрируют, как эти методы могут сэкономить ваше время на чтение и написание кода.

CountBy

Используйте countBy, если вы хотите подсчитать набор вещей по атрибуту или результату применения Function к этим вещам. Метод принимает в качестве параметра тип Function и возвращает Bag. Этот метод был добавлен в Eclipse Collections 9.0.

Следующий код - это обязательное решение Java для взятия List из PetType и подсчета каждого PetType с использованием Map.

List<PetType> petTypes =
    this.people.flatCollect(Person::getPets).collect(Pet::getType);

Map<PetType, Integer> counts = Maps.mutable.empty();
for (PetType petType : petTypes)
{
    Integer count = petTypeCounts.get(petType);
    if (count == null)
    {
        count = 0;
    }
    petTypeCounts.put(petType, count + 1);
}
Assert.assertEquals(Integer.valueOf(2), counts.get(PetType.CAT));
Assert.assertEquals(Integer.valueOf(2), counts.get(PetType.DOG));
// The following test would fail as the Map would return null
Assert.assertEquals(Integer.valueOf(0), counts.get(PetType.CONDOR));

Этот код можно упростить, используя Java 8 Streams и groupingBy и counting Collectors.

Map<PetType, Long> counts =
    this.people.stream()
        .flatMap(person -> person.getPets().stream())
        .collect(Collectors.groupingBy(Pet::getType,
                                       Collectors.counting()));

Assert.assertEquals(Long.valueOf(2), counts.get(PetType.CAT));
Assert.assertEquals(Long.valueOf(2), counts.get(PetType.DOG));
// The following test would fail as the Map would still return null
Assert.assertEquals(Long.valueOf(0), counts.get(PetType.CONDOR));

В следующем решении будет использоваться тип Eclipse Collections Bag вместо Map. Bag - это Map ключей к счетчикам. Реализация HashBag в Eclipse Collections использует ObjectIntHashMap для своего хранилища, поэтому не требует упаковки счетчиков как Integer или Long.

Bag<PetType> counts =
    this.people.asLazy()
        .flatCollect(Person::getPets)
        .collect(Pet::getType)
        .toBag();
Assert.assertEquals(2, counts.occurrencesOf(PetType.CAT));
Assert.assertEquals(2, counts.occurrencesOf(PetType.DOG));
Assert.assertEquals(0, counts.occurrencesOf(PetType.CONDOR));

Для чтения этого кода требуется меньше времени, поскольку в нем меньше символов, но метод toBag сообщит вам о своем намерении только в том случае, если вы уже знаете, что такое Bag.

Вот небольшая модификация этого решения с использованием countBy.

Bag<PetType> counts =
    this.people.asLazy()
        .flatCollect(Person::getPets)
        .countBy(Pet::getType);

Assert.assertEquals(2, counts.occurrencesOf(PetType.CAT));
Assert.assertEquals(2, counts.occurrencesOf(PetType.DOG));
Assert.assertEquals(0, counts.occurrencesOf(PetType.CONDOR));

CountBy сокращает код и лучше передает намерения. Считаем всех питомцев по их PetType. Результатом будет Bag, так как это лучшая структура данных для возврата.

Группа по

Используйте groupBy, если вы хотите сгруппировать элементы коллекции с помощью функции для вычисления ключа для каждого элемента. Метод принимает тип Function в качестве параметра и возвращает Multimap.

Следующий код - это обязательное решение Java, позволяющее взять List из Person и сгруппировать их по фамилиям.

Map<String, List<Person>> lastNamesToPeople = Maps.mutable.empty();
for (Person person : this.people)
{
    String lastName = person.getLastName();
    List<Person> peopleWithLastName =
        lastNamesToPeople.get(lastName);
    if (peopleWithLastName == null)
    {
        peopleWithLastName = Lists.mutable.empty();
        lastNamesToPeople.put(lastName, peopleWithLastName);
    }
    peopleWithLastName.add(person);
}
Assert.assertEquals(3, lastNamesToPeople.get("Smith").size());

Этот код можно упростить, используя Java 8 Streams и метод groupingBy на Collectors.

Map<String, List<Person>> lastNamesToPeople = 
    this.people.stream()
        .collect(Collectors.groupingBy(Person::getLastName));
Assert.assertEquals(3, lastNamesToPeople.get("Smith").size());
// This code will throw a NullPointerException
Assert.assertEquals(0, lastNamesToPeople.get("Smith1").size());

Этот код можно еще больше упростить, используя groupBy из Eclipse Collections. В отличие от groupingBy на Collectors, который возвращает Map и возвращает null для отсутствующих ключей, groupBy возвращает Multimap. Multimap вернет пустые коллекции для отсутствующих ключей.

Multimap<String, Person> lastNamesToPeople =
    this.people.groupBy(Person::getLastName);

Assert.assertEquals(3, lastNamesToPeople.get("Smith").size());
// This assertion passes, as a Multimap will return an empty list
Assert.assertEquals(0, lastNamesToPeople.get("Smith1").size());

GroupByEach

Используйте groupByEach, если вы хотите сгруппировать элементы коллекции вместе с помощью функции, которая вычисляет несколько ключей для каждого элемента. Метод принимает в качестве параметра тип Function и возвращает Multimap. Function должен возвращать подтип Iterable.

Вот обязательный пример Java, который берет список Person и группирует их по PetType, которыми они владеют, чтобы убедиться, что PetType уникальны.

Map<PetType, Set<Person>> peopleByPetType = Maps.mutable.empty();

for (Person person : this.people)
{
    MutableList<Pet> pets = person.getPets();
    for (Pet pet : pets)
    {
        PetType petType = pet.getType();
        Set<Person> peopleWithPetType = 
            peopleByPetType.get(petType);
        if (peopleWithPetType == null)
        {
            peopleWithPetType = Sets.mutable.empty();
            peopleByPetType.put(petType, peopleWithPetType);
        }
        peopleWithPetType.add(person);
    }
}

Assert.assertEquals(2, peopleByPetType.get(PetType.CAT).size());
Assert.assertEquals(2, peopleByPetType.get(PetType.DOG).size());

Я не знаю, как сделать это, используя Java 8 Streams с Collectors сегодня, без создания моего собственного Collector. Вот решение, использующее метод Eclipse Collections groupByEach.

Multimap<PetType, Person> multimap =
    this.people.groupByEach(
         Person::getPetTypes,
         Multimaps.mutable.set.empty());

Assert.assertEquals(2, multimap.get(PetType.CAT).size());
Assert.assertEquals(2, multimap.get(PetType.DOG).size());

В приведенном выше примере мне нужно было передать пустой MutableSetMultimap в качестве целевой коллекции для заполнения, потому что по умолчанию было бы возвращено MutableListMultimap. Это потому, что this.people - это MutableList. Если бы поле this.people было SetIterable или подклассом, работало бы следующее.

Multimap<PetType, Person> multimap =
    this.people.groupByEach(Person::getPetTypes);

Assert.assertEquals(2, multimap.get(PetType.CAT).size());
Assert.assertEquals(2, multimap.get(PetType.DOG).size());

Не упускайте возможности сэкономить время

Такие высокоуровневые методы, как countBy, groupBy и groupByEach, могут количественно и качественно улучшить ваш код. Вы можете сэкономить себе и своей команде много времени в течение всего жизненного цикла проекта, используя API более высокого уровня, такие как By методы в Eclipse Collections.

Кстати, иногда мелочи жизни и API имеют большое значение.

Коллекции Eclipse открыты для взносов. Если вам нравится библиотека, вы можете сообщить нам об этом, отметив ее на GitHub.