«1. Обзор

Eclipse Collections — еще одна улучшенная структура коллекций для Java.

Проще говоря, он предоставляет оптимизированные реализации, а также некоторые дополнительные структуры данных и функции, которых нет в ядре Java.

Библиотека предоставляет как изменяемые, так и неизменяемые реализации всех структур данных.

2. Зависимость Maven

Давайте начнем с добавления следующей зависимости Maven в наш pom.xml:

<dependency
    <groupId>org.eclipse.collections</groupId>
    <artifactId>eclipse-collections</artifactId>
    <version>8.2.0</version>
</dependency>

Мы можем найти последнюю версию библиотеки в центральном репозитории Maven.

3. Общая картина

3.1. Основные типы коллекций

Основные типы коллекций в коллекциях Eclipse:

    ListIterable — упорядоченная коллекция, которая поддерживает порядок вставки и позволяет дублировать элементы. Подинтерфейсы включают: MutableList, FixedSizeList и ImmutableList. Наиболее распространенной реализацией ListIterable является FastList, который является подклассом MutableList SetIterable — коллекции, не допускающей дублирования элементов. Он может быть сортированным или несортированным. Подинтерфейсы включают: SortedSetIterable и UnsortedSetIterable. Наиболее распространенной несортированной реализацией SetIterable является UnifiedSet MapIterable — набор пар ключ/значение. Подинтерфейсы включают MutableMap, FixedSizeMap и ImmutableMap. Двумя распространенными реализациями являются UnifiedMap и MutableSortedMap. В то время как UnifiedMap не поддерживает какой-либо порядок, MutableSortedMap поддерживает естественный порядок элементов BiMap — набор пар ключ/значение, которые можно повторять в любом направлении. BiMap расширяет интерфейс MapIterable Bag — неупорядоченная коллекция, допускающая дублирование. Подинтерфейсы включают MutableBag и FixedSizeBag. Наиболее распространенной реализацией является HashBag StackIterable — коллекция, которая поддерживает порядок «последний вошел, первый ушел», перебирая элементы в обратном порядке вставки. Подинтерфейсы включают MutableStack и ImmutableStack MultiMap — набор пар ключ/значение, допускающий несколько значений для каждого ключа

3.2. Примитивные коллекции

Фреймворк также предоставляет огромный набор примитивных коллекций; их реализации названы в честь типа, который они содержат. Для каждого из них существуют изменяемые, неизменяемые, синхронизированные и немодифицируемые формы:

    Примитивные списки Примитивные наборы Примитивные стеки Примитивные сумки Примитивные карты IntInterval

Существует огромное количество примитивных форм карт, охватывающих все возможные комбинации как примитивных, так и ключи объекта и либо примитивные, либо объектные значения.

Небольшое примечание: IntInterval — это диапазон целых чисел, которые можно повторять с использованием значения шага.

4. Создание экземпляра коллекции

Чтобы добавить элементы в ArrayList или HashSet, мы создаем экземпляр коллекции, вызывая конструктор без аргументов, а затем добавляя каждый элемент один за другим.

Хотя мы все еще можем делать это в коллекциях Eclipse, мы также можем создать экземпляр коллекции и предоставить все исходные элементы одновременно в одной строке.

Давайте посмотрим, как мы можем создать экземпляр FastList:

MutableList<String> list = FastList.newListWith(
  "Porsche", "Volkswagen", "Toyota", "Mercedes", "Toyota");

Точно так же мы можем создать экземпляр UnifiedSet и добавить в него элементы, передав элементы статическому методу newSetWith():

Set<String> comparison = UnifiedSet.newSetWith(
  "Porsche", "Volkswagen", "Toyota", "Mercedes");

Вот как мы можем создать экземпляр HashBag:

MutableBag<String> bag = HashBag.newBagWith(
  "Porsche", "Volkswagen", "Toyota", "Porsche", "Mercedes");

Создание экземпляров карт и добавление к ним пар ключ-значение аналогично. Единственное отличие состоит в том, что мы передаем пары ключей и значений методу newMapWith() как реализации интерфейса Pair.

Возьмем в качестве примера UnifiedMap:

Pair<Integer, String> pair1 = Tuples.pair(1, "One");
Pair<Integer, String> pair2 = Tuples.pair(2, "Two");
Pair<Integer, String> pair3 = Tuples.pair(3, "Three");

UnifiedMap<Integer, String> map = new UnifiedMap<>(pair1, pair2, pair3);

Мы по-прежнему можем использовать подход API коллекций Java: как add() и remove().

UnifiedMap<Integer, String> map = new UnifiedMap<>();

map.put(1, "one");
map.put(2, "two");
map.put(3, "three");

Неизменяемые коллекции, однако, позволяют нам вызывать эти методы, но в этом случае будет выброшено исключение UnsupportedOperationException.

5. Получение элементов из коллекций

Как и при использовании стандартных списков, элементы списков коллекций Eclipse можно получить по их индексу:

А значения карт коллекций Eclipse можно получить с помощью их ключа: ~ ~~

list.get(0);

«Методы getFirst() и getLast() можно использовать для получения соответственно первого и последнего элементов списка. В случае других коллекций они возвращают первый и последний элементы, которые были бы возвращены итератором.

map.get(0);

Методы max() и min() можно использовать для получения максимального и минимального значений коллекции на основе естественного порядка.

map.getFirst();
map.getLast();

6. Перебор коллекций

map.max();
map.min();

Eclipse Collections предоставляет множество способов перебора коллекций. Давайте посмотрим, что они из себя представляют и как они работают на практике.

6.1. Фильтрация коллекций

Шаблон выбора возвращает новую коллекцию, содержащую элементы коллекции, которые удовлетворяют логическому условию. По сути, это операция фильтрации.

Вот пример:

То же самое можно сделать с помощью простого лямбда-выражения:

@Test
public void givenListwhenSelect_thenCorrect() {
    MutableList<Integer> greaterThanThirty = list
      .select(Predicates.greaterThan(30))
      .sortThis();
    
    Assertions.assertThat(greaterThanThirty)
      .containsExactly(31, 38, 41);
}

Шаблон отклонения противоположен. Он возвращает коллекцию всех элементов, которые не удовлетворяют логическому условию.

return list.select(i -> i > 30)
  .sortThis();

Давайте рассмотрим пример:

Здесь мы отвергаем все элементы, которые больше 30.

@Test
public void whenReject_thenCorrect() {
    MutableList<Integer> notGreaterThanThirty = list
      .reject(Predicates.greaterThan(30))
      .sortThis();
    
    Assertions.assertThat(notGreaterThanThirty)
      .containsExactlyElementsOf(this.expectedList);
}

6.2. Метод collect()

Метод collect возвращает новую коллекцию, элементами которой являются результаты, возвращаемые предоставленным лямбда-выражением, — по сути, это комбинация методов map() и collect() из Stream API.

Давайте посмотрим на это в действии:

Созданная коллекция lastNames содержит фамилии, собранные из списка студентов.

@Test
public void whenCollect_thenCorrect() {
    Student student1 = new Student("John", "Hopkins");
    Student student2 = new Student("George", "Adams");
    
    MutableList<Student> students = FastList
      .newListWith(student1, student2);
    
    MutableList<String> lastNames = students
      .collect(Student::getLastName);
    
    Assertions.assertThat(lastNames)
      .containsExactly("Hopkins", "Adams");
}

Но что, если возвращаемая коллекция является коллекцией коллекций, и мы не хотим поддерживать вложенную структуру?

Например, если у каждого студента есть несколько адресов и нам нужна коллекция, содержащая адреса в виде строк, а не коллекция коллекций, мы можем использовать метод flatCollect().

Вот пример:

6.3. Обнаружение элемента

@Test
public void whenFlatCollect_thenCorrect() {
    MutableList<String> addresses = students
      .flatCollect(Student::getAddresses);
    
    Assertions.assertThat(addresses)
      .containsExactlyElementsOf(this.expectedAddresses);
}

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

Рассмотрим краткий пример:

Метод anySatisfy определяет, удовлетворяет ли какой-либо элемент коллекции логическому условию.

@Test
public void whenDetect_thenCorrect() {
    Integer result = list.detect(Predicates.greaterThan(30));
    
    Assertions.assertThat(result)
      .isEqualTo(41);
}

Вот пример:

Точно так же метод allSatisfy определяет, удовлетворяют ли все элементы коллекции логическому условию.

@Test
public void whenAnySatisfiesCondition_thenCorrect() {
    boolean result = list.anySatisfy(Predicates.greaterThan(30));
    
    assertTrue(result);
}

Давайте рассмотрим быстрый пример:

6.4. Метод partition()

@Test
public void whenAnySatisfiesCondition_thenCorrect() {
    boolean result = list.allSatisfy(Predicates.greaterThan(0));
    
    assertTrue(result);
}

Метод partition выделяет каждый элемент коллекции в одну из двух коллекций в зависимости от того, удовлетворяет ли элемент логическому условию.

Рассмотрим пример:

6.5. Ленивая итерация

@Test
public void whenAnySatisfiesCondition_thenCorrect() {
    MutableList<Integer> numbers = list;
    PartitionMutableList<Integer> partitionedFolks = numbers
      .partition(i -> i > 30);
	
    MutableList<Integer> greaterThanThirty = partitionedFolks
      .getSelected()
      .sortThis();
    MutableList<Integer> smallerThanThirty = partitionedFolks
      .getRejected()
      .sortThis();
    
    Assertions.assertThat(smallerThanThirty)
      .containsExactly(1, 5, 8, 17, 23);
    Assertions.assertThat(greaterThanThirty)
      .containsExactly(31, 38, 41);
}

Ленивая итерация — это шаблон оптимизации, в котором метод итерации вызывается, но его фактическое выполнение откладывается до тех пор, пока его действие или возвращаемые значения не потребуются другим последующим методом.

Здесь объект lazyStudents не извлекает элементы списка студентов, пока не будет вызван метод collect().

@Test
public void whenLazyIteration_thenCorrect() {
    Student student1 = new Student("John", "Hopkins");
    Student student2 = new Student("George", "Adams");
    Student student3 = new Student("Jennifer", "Rodriguez");

    MutableList<Student> students = Lists.mutable
      .with(student1, student2, student3);
    LazyIterable<Student> lazyStudents = students.asLazy();
    LazyIterable<String> lastNames = lazyStudents
      .collect(Student::getLastName);
    
    Assertions.assertThat(lastNames)
      .containsAll(Lists.mutable.with("Hopkins", "Adams", "Rodriguez"));
}

7. Сопряжение элементов коллекции

Метод zip() возвращает новую коллекцию, объединяя элементы двух коллекций в пары. Если какая-либо из двух коллекций длиннее, остальные элементы будут усечены.

Давайте посмотрим, как мы можем его использовать:

Мы также можем соединить элементы коллекции с их индексами, используя метод zipWithIndex():

@Test
public void whenZip_thenCorrect() {
    MutableList<String> numbers = Lists.mutable
      .with("1", "2", "3", "Ignored");
    MutableList<String> cars = Lists.mutable
      .with("Porsche", "Volvo", "Toyota");
    MutableList<Pair<String, String>> pairs = numbers.zip(cars);
    
    Assertions.assertThat(pairs)
      .containsExactlyElementsOf(this.expectedPairs);
}

8. Преобразование коллекций

@Test
public void whenZip_thenCorrect() {
    MutableList<String> cars = FastList
      .newListWith("Porsche", "Volvo", "Toyota");
    MutableList<Pair<String, Integer>> pairs = cars.zipWithIndex();
    
    Assertions.assertThat(pairs)
      .containsExactlyElementsOf(this.expectedPairs);
}

Eclipse Collections обеспечивает простые методы преобразования типа контейнера в другой. Это методы toList(), toSet(), toBag() и toMap().

Давайте посмотрим, как мы можем их использовать:

Давайте запустим наш тест:

public static List convertToList() {
    UnifiedSet<String> cars = new UnifiedSet<>();
    
    cars.add("Toyota");
    cars.add("Mercedes");
    cars.add("Volkswagen");
    
    return cars.toList();
}

9. Заключение

@Test
public void whenConvertContainerToAnother_thenCorrect() {
    MutableList<String> cars = (MutableList) ConvertContainerToAnother 
      .convertToList();
    
    Assertions.assertThat(cars)
      .containsExactlyElementsOf(
      FastList.newListWith("Volkswagen", "Toyota", "Mercedes"));
}

В этом руководстве мы рассмотрели краткий обзор коллекций Eclipse и функции, которые они предоставляют.

Полная реализация этого руководства доступна на GitHub.

«