Использование потоков для сбора в набор деревьев с помощью пользовательского компаратора


работая в Java 8, у меня есть TreeSet определен следующим образом:

private TreeSet<PositionReport> positionReports = 
        new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp));

PositionReport - это довольно простой класс, как это:

public static final class PositionReport implements Cloneable {
    private final long timestamp;
    private final Position position;

    public static PositionReport create(long timestamp, Position position) {
        return new PositionReport(timestamp, position);
    }

    private PositionReport(long timestamp, Position position) {
        this.timestamp = timestamp;
        this.position = position;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public Position getPosition() {
        return position;
    }
}

это прекрасно работает.

теперь я хочу удалить записи из TreeSet positionReports здесь timestamp старше некоторого значения. Но я не могу понять правильный синтаксис Java 8, чтобы выразить это.

эта попытка на самом деле компилируется, но дает мне новый TreeSet с неустановленным компаратор:

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(Collectors.toCollection(TreeSet::new))

как мне выразить, что я хочу собрать в TreeSet С компаратором, такие как Comparator.comparingLong(PositionReport::getTimestamp) ?

Я бы подумал что-то вроде

positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(
                TreeSet::TreeSet(Comparator.comparingLong(PositionReport::getTimestamp))
            )
        );

но это не компилируется / кажется допустимым синтаксисом для ссылок на методы.

5 56

5 ответов:

Comparator<PositionReport> byTimestamp =
    Comparator.comparingLong(PositionReport::getTimestamp);

Supplier<TreeSet<PositionReport>> supplier =
    () -> new TreeSet<PositionReport>(byTimestamp);

positionReports = positionReports.stream()
                                 .filter(p -> p.getTimeStamp() >= oldestKept)
                                 .collect(Collectors.toCollection(supplier));

Это просто использовать следующий код:

    positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(()->new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp)
)));

вы можете просто преобразовать в SortedSet в конце (при условии, что вы не возражаете против дополнительной копии).

positionReports = positionReports
                .stream()
                .filter(p -> p.getTimeStamp() >= oldestKept)
                .collect(Collectors.toSet());

return new TreeSet(positionReports);

существует метод сбора для этого без использования потоков:default boolean removeIf(Predicate<? super E> filter). Смотрите документация.

Так что ваш код может выглядеть так:

positionReports.removeIf(p -> p.timestamp < oldestKept);

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

обнаружение дубликатов должно выполняться отдельным правильным методом хэш-кода элементов. Я предпочитаю использовать простой хэш-набор, чтобы предотвратить дубликаты с хэш-кодом, учитывая все свойства (id и имя в пример) и возвращает простой отсортированный список при получении элементов (сортировка только по имени в Примере):

public class ProductAvailableFiltersDTO {

    private Set<FilterItem> category_ids = new HashSet<>();

    public List<FilterItem> getCategory_ids() {
        return category_ids.stream()
            .sorted(Comparator.comparing(FilterItem::getName))
            .collect(Collectors.toList());
    }

    public void setCategory_ids(List<FilterItem> category_ids) {
        this.category_ids.clear();
        if (CollectionUtils.isNotEmpty(category_ids)) {
            this.category_ids.addAll(category_ids);
        }
    }
}


public class FilterItem {
    private String id;
    private String name;

    public FilterItem(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FilterItem)) return false;
        FilterItem that = (FilterItem) o;
        return Objects.equals(getId(), that.getId()) &&
                Objects.equals(getName(), that.getName());
    }

    @Override
    public int hashCode() {

        return Objects.hash(getId(), getName());
    }
}