Сортировка в Java

Сортировка в Java

1. обзор

В этой статье будет показано, как применить сортировку кArray,List,Set иMap в Java 7 и Java 8.

2. Сортировка сArray

Давайте начнем с сортировки целочисленных массивов, используя методArrays.sort().

Мы определим следующие массивыint в методе jUnit@Before:

@Before
public void initVariables () {
    toSort = new int[]
      { 5, 1, 89, 255, 7, 88, 200, 123, 66 };
    sortedInts = new int[]
      {1, 5, 7, 66, 88, 89, 123, 200, 255};
    sortedRangeInts = new int[]
      {5, 1, 89, 7, 88, 200, 255, 123, 66};
    ...
}

2.1. Сортировка полного массива

Теперь воспользуемся простым APIArray.sort():

@Test
public void givenIntArray_whenUsingSort_thenSortedArray() {
    Arrays.sort(toSort);

    assertTrue(Arrays.equals(toSort, sortedInts));
}

Несортированный массив теперь полностью отсортирован:

[1, 5, 7, 66, 88, 89, 123, 200, 255]

As mentioned in the official JavaDoc,Arrays.sort использует быструю сортировку с двойным поворотом наprimitives. Он предлагает производительность O (n log (n)) и, как правило, быстрее, чем традиционные реализации (с одним поворотом) Quicksort. Однако он использует стабильную адаптивную итеративную реализациюmergesort algorithm for Array of Objects.

2.2. Сортировка части массива

Arrays.sort имеет еще один APIsort, который мы обсудим здесь:

Arrays.sort(int[] a, int fromIndex, int toIndex)

Это позволит отсортировать только часть массива между двумя индексами.

Давайте посмотрим на небольшой пример:

@Test
public void givenIntArray_whenUsingRangeSort_thenRangeSortedArray() {
    Arrays.sort(toSort, 3, 7);

    assertTrue(Arrays.equals(toSort, sortedRangeInts));
}

Сортировка будет выполняться только по следующим элементам подмассива (toIndex будет исключительным):

[255, 7, 88, 200]

Результирующий отсортированный подмассив включительно с основным массивом будет:

[5, 1, 89, 7, 88, 200, 255, 123, 66]

2.3. Java 8Arrays.sort противArrays.parallelSort

Java 8 поставляется с новым API -parallelSort - с подписью, аналогичной APIArrays.sort():

@Test
public void givenIntArray_whenUsingParallelSort_thenArraySorted() {
    Arrays.parallelSort(toSort);

    assertTrue(Arrays.equals(toSort, sortedInts));
}

За кулисамиparallelSort(), он разбивает массив на разные подмассивы (в соответствии с гранулярностью алгоритмаparallelSort). Каждый подмассив сортируется сArrays.sort() в разных потоках, так чтоsort может выполняться параллельно и окончательно объединяться в отсортированный массив.

Обратите внимание, чтоForJoin common pool используется для выполнения этих параллельных задач и последующего объединения результатов.

РезультатArrays.parallelSort, конечно же, будет таким же, как иArray.sort, это просто вопрос использования многопоточности.

Наконец, есть аналогичные варианты APIArrays.sort вArrays.parallelSort:

Arrays.parallelSort (int [] a, int fromIndex, int toIndex);

3. СортировкаList

Теперь давайте воспользуемся APICollections.sort() вjava.utils.Collections - чтобы отсортироватьList целых чисел:

@Test
public void givenList_whenUsingSort_thenSortedList() {
    List toSortList = Ints.asList(toSort);
    Collections.sort(toSortList);

    assertTrue(Arrays.equals(toSortList.toArray(),
    ArrayUtils.toObject(sortedInts)));
}

List перед сортировкой будет содержать следующие элементы:

[5, 1, 89, 255, 7, 88, 200, 123, 66]

И естественно, после сортировки:

[1, 5, 7, 66, 88, 89, 123, 200, 255]

As mentioned in Oracle JavaDoc дляCollections.Sort, он использует модифицированную сортировку слиянием и предлагает гарантированную производительностьn log(n).

4. СортировкаSet

Затем давайте воспользуемсяCollections.sort() для сортировкиLinkedHashSet.

Мы используемLinkedHashSet, потому что он поддерживает порядок размещения.

Обратите внимание на то, как использовать APIsort вCollections -we’re first wrapping the set in a list:

@Test
public void givenSet_whenUsingSort_thenSortedSet() {
    Set integersSet = new LinkedHashSet<>(Ints.asList(toSort));
    Set descSortedIntegersSet = new LinkedHashSet<>(
      Arrays.asList(new Integer[]
        {255, 200, 123, 89, 88, 66, 7, 5, 1}));

    List list = new ArrayList(integersSet);
    Collections.sort(list, (i1, i2) -> {
        return i2 - i1;
    });
    integersSet = new LinkedHashSet<>(list);

    assertTrue(Arrays.equals(
      integersSet.toArray(), descSortedIntegersSet.toArray()));
}

5. СортировкаMap

В этом разделе мы начнем сsorting a Map – both by keys and by values.

Давайте сначала определим карту, которую мы будем сортировать:

@Before
public void initVariables () {
    ....
    HashMap map = new HashMap<>();
    map.put(55, "John");
    map.put(22, "Apple");
    map.put(66, "Earl");
    map.put(77, "Pearl");
    map.put(12, "George");
    map.put(6, "Rocky");
    ....
}

5.1. СортировкаMap по ключам

Теперь мы извлечем записиkeys иvalues изHashMap и отсортируем их на основе значений ключей в этом примере:

@Test
public void givenMap_whenSortingByKeys_thenSortedMap() {
    Integer[] sortedKeys = new Integer[] { 6, 12, 22, 55, 66, 77 };

    List> entries
      = new ArrayList<>(map.entrySet());
    Collections.sort(entries, new Comparator>() {
        @Override
        public int compare(
          Entry o1, Entry o2) {
            return o1.getKey().compareTo(o2.getKey());
        }
    });
    Map sortedMap = new LinkedHashMap<>();
    for (Map.Entry entry : entries) {
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    assertTrue(Arrays.equals(sortedMap.keySet().toArray(), sortedKeys));
}

Обратите внимание, как мы использовалиLinkedHashMap при копировании отсортированныхEntries на основе ключей (посколькуHashSet не гарантирует порядок ключей).

Map перед сортировкой:

[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]

Map после сортировкиby keys:

[Key: 6 , Value: Rocky]
[Key: 12 , Value: George]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 66 , Value: Earl]
[Key: 77 , Value: Pearl]

5.2. СортировкаMap по значениям

Здесь мы будем сравнивать значения записейHashMap для сортировки на основе значенийHashMap:

@Test
public void givenMap_whenSortingByValues_thenSortedMap() {
    String[] sortedValues = new String[]
      { "Apple", "Earl", "George", "John", "Pearl", "Rocky" };

    List> entries
      = new ArrayList<>(map.entrySet());
    Collections.sort(entries, new Comparator>() {
        @Override
        public int compare(
          Entry o1, Entry o2) {
            return o1.getValue().compareTo(o2.getValue());
        }
    });
    Map sortedMap = new LinkedHashMap<>();
    for (Map.Entry entry : entries) {
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    assertTrue(Arrays.equals(sortedMap.values().toArray(), sortedValues));
}

Map перед сортировкой:

[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]

Map после сортировкиby values:

[Key: 22 , Value: Apple]
[Key: 66 , Value: Earl]
[Key: 12 , Value: George]
[Key: 55 , Value: John]
[Key: 77 , Value: Pearl]
[Key: 6 , Value: Rocky]

6. Сортировка настраиваемых объектов

Теперь поработаем с настраиваемым объектом:

public class Employee implements Comparable {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        ...
    }

    // standard getters, setters and toString
}

Мы будем использовать следующий массивEmployee для примера сортировки в следующих разделах:

@Before
public void initVariables () {
    ....
    employees = new Employee[] {
      new Employee("John", 23, 5000), new Employee("Steve", 26, 6000),
      new Employee("Frank", 33, 7000), new Employee("Earl", 43, 10000),
      new Employee("Jessica", 23, 4000), new Employee("Pearl", 33, 6000)};

    employeesSorted = new Employee[] {
      new Employee("Earl", 43, 10000), new Employee("Frank", 33, 70000),
      new Employee("Jessica", 23, 4000), new Employee("John", 23, 5000),
      new Employee("Pearl", 33, 4000), new Employee("Steve", 26, 6000)};

    employeesSortedByAge = new Employee[] {
      new Employee("John", 23, 5000), new Employee("Jessica", 23, 4000),
      new Employee("Steve", 26, 6000), new Employee("Frank", 33, 70000),
      new Employee("Pearl", 33, 4000), new Employee("Earl", 43, 10000)};
}

Мы можем сортировать массивы или коллекции пользовательских объектов:

  1. в естественном порядке (с использованием интерфейсаComparable) или

  2. в порядке, обеспечиваемомComparatorInterface

6.1. Using Comparableс

The natural order in java означает порядок, в котором примитив или объект должны быть упорядоченно отсортированы в данном массиве или коллекции.

Иjava.util.Arrays, иjava.util.Collections имеют методsort(), аIt’s highly recommended that natural orders should be consistent with the semantics of equals.

В этом примере мы будем считать сотрудников с одинаковымname равными:

@Test
public void givenEmpArray_SortEmpArray_thenSortedArrayinNaturalOrder() {
    Arrays.sort(employees);

    assertTrue(Arrays.equals(employees, employeesSorted));
}

Вы можете определить естественный порядок элементов, реализовав интерфейсComparable, который имеет методcompareTo() для сравнения текущего объекта и объекта, переданного в качестве аргумента.

Чтобы понять это ясно, давайте посмотрим на пример классаEmployee, который реализует интерфейсComparable:

public class Employee implements Comparable {
    ...

    @Override
    public boolean equals(Object obj) {
        return ((Employee) obj).getName().equals(getName());
    }

    @Override
    public int compareTo(Object o) {
        Employee e = (Employee) o;
        return getName().compareTo(e.getName());
    }
}

Обычно логика сравнения записывается методомcompareTo. Здесь мы сравниваем заказ сотрудника илиname поля сотрудника. Два сотрудника будут равны, если у них одинаковое имя.

Теперь, когда в приведенном выше коде вызываетсяArrays.sort(employees);, мы знаем, какова логика и порядок сортировки сотрудников по возрасту:

[("Earl", 43, 10000),("Frank", 33, 70000), ("Jessica", 23, 4000),
 ("John", 23, 5000),("Pearl", 33, 4000), ("Steve", 26, 6000)]

Мы видим, что массив отсортирован по имени служащего - теперь это естественный порядок для классаEmployee.

6.2. ИспользуяComparator

Теперь давайте отсортируем элементы, используя реализацию интерфейсаComparator - где мы передаем анонимный внутренний класс на лету в APIArrays.sort():

@Test
public void givenIntegerArray_whenUsingSort_thenSortedArray() {
    Integer [] integers = ArrayUtils.toObject(toSort);
    Arrays.sort(integers, new Comparator() {
        @Override
        public int compare(Integer a, Integer b) {
            return a - b;
        }
    });

    assertTrue(Arrays.equals(integers, ArrayUtils.toObject(sortedInts)));
}

Теперь давайте отсортируем сотрудников поsalary и передадим другую реализацию компаратора:

Arrays.sort(employees, new Comparator() {
    @Override
    public int compare(Employee o1, Employee o2) {
       return (int) (o1.getSalary() - o2.getSalary());
    }
 });

Отсортированные массивы сотрудников на основеsalary будут:

[(Jessica,23,4000.0), (John,23,5000.0), (Pearl,33,6000.0), (Steve,26,6000.0),
(Frank,33,7000.0), (Earl,43,10000.0)]

Обратите внимание, что мы можем использоватьCollections.sort() аналогичным образом для сортировкиList иSet объектов в естественном или произвольном порядке, как описано выше для массивов.

7. Сортировка с помощью лямбда-выражений

Начиная с Java 8, мы можем использовать Lambdas для реализации функционального интерфейсаComparator.

Вы можете ознакомиться с описаниемLambdas in Java 8, чтобы освежить синтаксис.

Заменим старый компаратор:

Comparator c  = new Comparator<>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a - b;
    }
}

С эквивалентной реализацией, используя лямбда-выражение:

Comparator c = (a, b) -> a - b;

Наконец, давайте напишем тест:

@Test
public void givenArray_whenUsingSortWithLambdas_thenSortedArray() {
    Integer [] integersToSort = ArrayUtils.toObject(toSort);
    Arrays.sort(integersToSort, (a, b) -> {
        return a - b;
    });

    assertTrue(Arrays.equals(integersToSort,
      ArrayUtils.toObject(sortedInts)));
}

Как видите, здесь гораздо понятнее и лаконичнее логика.

8. ИспользуяComparator.comparing иComparator.thenComparing

Java 8 поставляется с двумя новыми API, полезными для сортировки -comparing() иthenComparing() в интерфейсеComparator.

Они очень удобны для объединения нескольких условийComparator.

Давайте рассмотрим сценарий, в котором мы можем захотеть сравнитьEmployee поage, а затем поname:

@Test
public void givenArrayObjects_whenUsingComparing_thenSortedArrayObjects() {
    List employeesList = Arrays.asList(employees);
    employees.sort(Comparator.comparing(Employee::getAge));

    assertTrue(Arrays.toString(employees.toArray())
      .equals(sortedArrayString));
}

В этом примереEmployee::getAge - это ключ сортировки для интерфейсаComparator, реализующего функциональный интерфейс с функцией сравнения.

Вот массив сотрудников после сортировки:

[(John,23,5000.0), (Jessica,23,4000.0), (Steve,26,6000.0), (Frank,33,7000.0),
(Pearl,33,6000.0), (Earl,43,10000.0)]

Здесь сотрудники отсортированы поage.

Мы видим, чтоJohn иJessica имеют одинаковый возраст - это означает, что логика порядка теперь должна учитывать их имена - чего мы можем достичь с помощьюthenComparing():

...
employees.sort(Comparator.comparing(Employee::getAge)
  .thenComparing(Employee::getName));
...

После сортировки с использованием приведенного выше фрагмента кода элементы в массиве employee будут отсортированы следующим образом:

[(Jessica,23,4000.0),
 (John,23,5000.0),
 (Steve,26,6000.0),
 (Frank,33,7000.0),
 (Pearl,33,6000.0),
 (Earl,43,10000.0)
]

Таким образом,comparing() иthenComparing() определенно упрощают реализацию более сложных сценариев сортировки.

Дальнейшее чтение:

Руководство по сортировке в Котлине

Краткое руководство по сортировке с использованием стандартной библиотеки Kotlin.

Read more

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

В этой статье мы увидели, как применить сортировку кArray,List,Set иMap.

Мы также увидели краткое введение о том, как функции Java 8 могут быть полезны при сортировке, такие как использование лямбда-выражений,comparing(),thenComparing() иparallelSort().

Все примеры, использованные в статье, доступныover on GitHub.