Сортировка List<> по значению поля

Всем привет!
У меня вопрос касательно того, как можно красиво и наиболее правильно оформить сортировку List<> по значению.

Какого-то определённого термина обозначения данной сортировки я не нашёл, поэтому назвал её "по значению" (если кто-то считает, что можно навзвать более выраженно, то милости просим).

Итак, в чём суть:

Есть класс Product

@Data
public class Product {

    /**
     * Тип продукта
     */
    private ProductType productType;

    /**
     * Доступность продукта (продукт может быть временно заблокирован и не доступен)
     */
    private Boolean isAvailable;
    
    private enum ProductType {
        NEW,
        OLD,
        REMOVED,
        UNUSED,
        EXCLUSIVE
    }

}

Предположим, что данный класс хранится в Базе Данных и когда я его вытаскиваю от туда, то получаю List<Product>, а затем я должен вернуть его на FRONTEND в отсортированном виде.

Заказчик выдвинул требования.
Упорядоченность в списке должна быть следующая:

  1. ProductType == EXCLUSIVE И isAvailable=true
  2. ProductType == NEW И isAvailable=true
  3. ProductType == OLD И isAvailable=true
  4. ProductType == UNUSED И isAvailable=true
  5. ProductType == REMOVED И isAvailable=true
  6. Все оставшиеся продукты у которых isAvailable=false

Вопрос насущный: как правильно организовать такого рода сортировку.

Если вы хотите знать смысл такого рода сортировки, то это не должно быть вашей заботой. ВАМ выдали ВОТ ТАКИЕ ТРЕБОВАНИЯ и нужно сделать так, как требуют.

У меня была идея сделать что-то вроде такого, а именно модернизировать класс Product, добавив метод для определения приоритета, а потом использовать Comparator.

@Data
public class Product {

    /**
     * Тип продукта
     */
    private ProductType productType;

    /**
     * Доступность продукта (продукт может быть временно заблокирован и не доступен)
     */
    private Boolean isAvailable;

    public int getPriority() {
        if (productType.equals(ProductType.EXCLUSIVE) && BooleanUtils.isTrue(isAvailable)) {
            return 1;
        } else if (productType.equals(ProductType.NEW) && BooleanUtils.isTrue(isAvailable)) {
            return 2;
        } else if (productType.equals(ProductType.OLD) && BooleanUtils.isTrue(isAvailable)) {
            return 3;
        } else if (productType.equals(ProductType.UNUSED) && BooleanUtils.isTrue(isAvailable)) {
            return 4;
        } else if (productType.equals(ProductType.REMOVED) && BooleanUtils.isTrue(isAvailable)) {
            return 5;
        }
        
        return 6;
    }
    
    private enum ProductType {
        NEW,
        OLD,
        REMOVED,
        UNUSED,
        EXCLUSIVE
    }

}

Использование

private static Comparator<Product> PRODUCT_SORT = Comparator.comparing(Product::getPriority)
        .reversed();

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


Ответы (2 шт):

Автор решения: IR42

Можно сделать массив с нужным порядком элементов и при сортировке сравнивать по индексу

var values = List.of("98", "99", "86", "41", "56", "46");
var list = new ArrayList<>(List.of("99", "99", "86", "46", "98", "98", "56", "41", "46"));
list.sort(Comparator.comparingInt(values::indexOf));


[98, 98, 99, 99, 86, 41, 56, 46, 46]
→ Ссылка
Автор решения: Roman-Stop RU aggression in UA

Если вы хотите знать смысл такого рода сортировки, то это не должно быть вашей заботой. ВАМ выдали ВОТ ТАКИЕ ТРЕБОВАНИЯ и нужно сделать так, как требуют.

Дело в том, что как раз важно понимать, почему требования такие. В этом случае, в частности, важно такой порядок важен только в этом конкретном сценарии, или он обусловлен порядком "важности" типов продуктов в любом сценарии. Я тут использую слово "важность", но правильней будет использовать то слово, которое используют в данной предметной области.

Реализация будет правильней и лучше, если модель предметной области будет ей соответствовать. А данном случае на практике это означает, что если тип продукта имеет "важность" (или "приоритет") сам по себе независимо от сценария, то имеет смысл это явно выразить в модели, это можно сделать добавив поле в enum:

    public int getPriority() {
        return BooleanUtils.isTrue(isAvailable) ? productType.getOrder() : 6;
    }
    
    private enum ProductType {
        NEW(1),
        OLD(2),
        REMOVED(3),
        UNUSED(4),
        EXCLUSIVE(5);

        int order;  // это и есть "важность"

        ProductType(int order) {
            this.order = order;
        }

        int getOrder() {
            return this.order;
        }
    }

Если же порядок сортировки зависит от сценария, то тогда, скорее неправильным будет размещать getPriority в Product. Лучше задать порядок для сценария например так:


ImmutableMap<ProductType, Integer> MAIN_PAGE_PRODUCT_ORDER = ImmutableMap.of(
      NEW, 1,
      OLD, 2,
      REMOVED, 3,
      UNUSED, 4,
      EXCLUSIVE, 5
);


private static Comparator<Product> sortBy(ImmutableMap<ProductType, Integer> productTypeOrder) {
   return Comparator.comparing(
           p -> p.isAvailable() ? productTypeOrder.get(p.getProductType()) : 6)
        .reversed();
}
→ Ссылка