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

Работая в 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))
            )
        );

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


person tbsalling    schedule 11.02.2014    source источник


Ответы (5)


Ссылки на методы нужны, когда у вас есть метод (или конструктор), который уже соответствует форме цели, которую вы пытаетесь удовлетворить. Вы не можете использовать ссылку на метод в этом случае, потому что фигура, на которую вы нацеливаетесь, — это Supplier, которая не принимает аргументов и возвращает коллекцию, но у вас есть конструктор TreeSet, который принимает аргумент, и вам нужно указать, что этот аргумент. Поэтому вам нужно использовать менее лаконичный подход и использовать лямбда-выражение:

TreeSet<Report> toTreeSet(Collection<Report> reports, long timestamp) {
    return reports.stream().filter(report -> report.timestamp() >= timestamp).collect(
        Collectors.toCollection(
            () -> new TreeSet<>(Comparator.comparingLong(Report::timestamp))
        )
    );
}
person gdejohn    schedule 11.02.2014
comment
Следует отметить, что вам не нужен компаратор, если тип вашего TreeSet (в данном случае PositionReport) реализует сопоставимый. - person xtrakBandit; 27.08.2015
comment
Продолжая @xtrakBandit — опять же, если вам не нужно указывать компаратор (естественная сортировка) — вы можете сделать это очень кратким: .collect(Collectors.toCollection(TreeSet::new)); - person Joshua Goldberg; 11.03.2016
comment
Я получил эту ошибку: toCollection in class Collectors cannot be applied to given types - person Bahadir Tasdemir; 17.08.2020
comment
@BahadirTasdemir Код работает. Убедитесь, что вы передаете только один аргумент Collectors::toCollection: Supplier, который возвращает Collection. Supplier — это тип с одним абстрактным методом, что означает, что он может быть целью лямбда-выражения, как в этом ответе. Лямбда-выражение не должно принимать аргументов (отсюда и пустой список аргументов ()) и возвращать коллекцию с типом элемента, который соответствует типу элементов в собираемом потоке (в данном случае TreeSet<PositionReport>). - person gdejohn; 17.08.2020

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

    positionReports = positionReports
        .stream()
        .filter(p -> p.timestamp >= oldestKept)
        .collect(
            Collectors.toCollection(()->new TreeSet<>(Comparator.comparingLong(PositionReport::getTimestamp)
)));
person Владимир Дворник    schedule 20.01.2017

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

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

return new TreeSet(positionReports);
person Daniel Scott    schedule 09.04.2015
comment
Вы должны быть осторожны при этом. Вы МОЖЕТЕ потерять элементы при этом. Как и в вопросе, заданном выше, естественный компаратор элементов отличается от того, который хочет использовать OP. Таким образом, при первоначальном преобразовании, поскольку это набор, он может потерять некоторые элементы, которых может не быть у другого компаратора (т.е. первый компаратор может вернуть compareTo() как 0, а другой может не иметь для некоторых сравнений. Все те, где compareTo() 0 теряется, так как это набор. ) - person looneyGod; 03.04.2017

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

Таким образом, ваш код может выглядеть так:

positionReports.removeIf(p -> p.timestamp < oldestKept);
person Michael Damone    schedule 26.06.2016

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

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

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());
    }
}
person Daniel Mora    schedule 20.07.2018