Stream API java Comparator
public Catalog sortCatalog(Catalog catalog, String configPath) {
//Тут я копирую каталог, чтобы старый не трогался
Catalog sortedCatalog = cloneCatalog(catalog);
//SortConfig - Это класс с двумя полями: имя поля по которому будем
//сортировать и порядок(asc|desc)
List<SortConfig> sortConfig = SortService.getConfigList(configPath);
//Каталог имеет одно поле - список катерорий.
//Каждая категория так же имеет список продуктов этой категории.
//Это обычные POJO
for (Category category : sortedCatalog.getCategories()) {
Stream<Product> productStream = category.getProducts().stream();
for (SortConfig config : sortConfig) {
productStream = productStream
.sorted(new CombinedProductComparator(config.getFieldName(), config.getOrder()));
//CombinedComparator - это компаратор, который сравнивает продукты
//в зависимости от того, какие значения в полях имеет SortConfig
//(тоесть, например, сортировка по цене asc)
}
category.setProducts(productStream.collect(Collectors.toList()));
}
return sortedCatalog;
}
Передо мной стоит задача: в SortConfig настраивается по xml-конфигурации. Например:
<?xml version="1.0" encoding="UTF-8"?>
<sort-criterias>
<name order="asc"/>
<price order="asc"/>
<rate order="desc"/>
</sort-criterias>
Мне необходимо сортировать сначала по имени, потом только цене и в конце по рейтингу. Но мой метод все поставил с ног на голову! По итогу за главный критерий сортировки он берет последнюю строчку конфигурации. Мне нужно это поправить. Я знаю, что можно корректно сортировать по sort(Comparator.thenComparing(...).thenComparing(...)) Но не могу даже представить как! Помогите пж(( Буду безмерно благодарна
Листинг классов выше:
public class CombinedProductComparator implements Comparator<Product> {
private final String fieldName;
private final Order order;
public CombinedProductComparator(String fieldName, Order order) {
this.fieldName = fieldName;
this.order = order;
}
@Override
public int compare(Product o1, Product o2) {
Comparator<Product> comparator;
switch (fieldName) {
case "name":
comparator = new ProductNameComparator();
break;
case "price":
comparator = new ProductPriceComparator();
break;
default:
case "rate":
comparator = new ProductRateComparator();
}
if (order == Order.DESC) comparator = comparator.reversed();
return comparator.compare(o1, o2);
}
}
@Data
@NoArgsConstructor
public class SortConfig {
private String fieldName;
private Order order;
public SortConfig(String fieldName, Order order) {
this.fieldName = fieldName;
this.order = order;
}
}
public class Catalog implements Serializable {
@Getter
@Setter
private List<Category> categories;
}
@Data
public abstract class Category implements Serializable {
protected String title;
protected List<Product> products;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Category category = (Category) o;
return title.equals(category.title);
}
@Override
public int hashCode() {
return Objects.hash(title);
}
}
@Data
@NoArgsConstructor
public class Product implements Serializable {
private String name;
private long priceInCents;
private int rate;
public Product(String name, long priceInCents, int rate) {
this.name = name;
this.priceInCents = priceInCents;
this.rate = rate;
}
}
Можно конечно сделать так:
public Catalog sortCatalog(Catalog catalog, String configPath) {
Catalog sortedCatalog = cloneCatalog(catalog);
List<SortConfig> sortConfig = SortService.getConfigList(configPath);
for (Category category : sortedCatalog.getCategories()) {
category.setProducts(category.getProducts().stream()
.sorted(new CombinedProductComparator(sortConfig.get(0).getFieldName(), sortConfig.get(0).getOrder())
.thenComparing(new CombinedProductComparator(sortConfig.get(1).getFieldName(), sortConfig.get(1).getOrder()))
.thenComparing(new CombinedProductComparator(sortConfig.get(2).getFieldName(), sortConfig.get(2).getOrder())))
.collect(Collectors.toList()));
}
return sortedCatalog;
}
Но что если sort-config.xml будет иметь переменное количество параметров сортировки?
Ответы (2 шт):
Создавайте компаратор как-то так:
Comparator<Product> combined = null;
for(SortConfig sortConfig : SortService.getConfigList(configPath)) {
if (combined == null) {
combined = new CombinedProductComparator(
sortConfig.getFieldName(), sortConfig.getOrder());
} else {
combined = combined.thenComparing(
new CombinedProductComparator(
sortConfig.getFieldName(), sortConfig.getOrder()));
}
}
Или вариант с reduce:
Comparator<Product> combined = sortService.getConfigList(configPath)
.stream()
.map(sortConfig -> new CombinedProductComparator(
sortConfig.getFieldName(), sortConfig.getOrder()))
.reduce(null, (combinedSoFar, nextComparator) ->
(combinedSoFar == null) ? nextComparator : combinedSoFar.thenComparing(nextComparator)
});
Ну и потом используйте уже составной компаратор для сортировки:
productStream = productStream.sorted(combined);
Поправилось, получилось так:
List<Comparator<Product>> comparators = SortService.getConfigList(configPath)
.stream()
.map(s -> new CombinedProductComparator(s.getFieldName(), s.getOrder()))
.collect(Collectors.toList());
Comparator<Product> combinedComparator = comparators.stream()
.reduce(null, (comp1, comp2) -> {
if (comp1 == null) {
return comp2;
} else {
return comp1.thenComparing(comp2);
}
});
Catalog sortedCatalog = cloneCatalog(catalog);
sortedCatalog.setCategories(sortedCatalog.getCategories().stream()
.peek(c -> c.getProducts().sort(combinedComparator))
.collect(Collectors.toList()));
return sortedCatalog;
Спасибо @RomanKonoval!