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

92

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

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

столовая
источник

Ответы:

118

Ссылки на методы предназначены для случаев, когда у вас есть метод (или конструктор), который уже соответствует форме цели, которую вы пытаетесь удовлетворить. Вы не можете использовать ссылку на метод в этом случае, потому что форма, на которую вы ориентируетесь, - это форма, 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))
        )
    );
}
Gdejohn
источник
4
Следует отметить, что вам не нужен компаратор, если тип вашего TreeSet (в данном случае PositionReport) реализует сопоставимый.
xtrakBandit
35
Продолжение @xtrakBandit - опять же, если вам не нужно указывать компаратор (естественная сортировка) - вы можете сделать его очень кратким:.collect(Collectors.toCollection(TreeSet::new));
Джошуа Голдберг,
Я получил эту ошибку:toCollection in class Collectors cannot be applied to given types
Бахадир Тасдемир
@BahadirTasdemir Код работает. Убедитесь, что вы передаете только один аргумент Collectors::toCollection: a, Supplierкоторый возвращает a Collection. Supplier- это тип с одним абстрактным методом, что означает, что он может быть целью лямбда-выражения, как в этом ответе. Лямбда-выражение не должно принимать аргументов (следовательно, пустой список аргументов ()) и возвращать коллекцию с типом элемента, который соответствует типу элементов в собираемом вами потоке (в данном случае a TreeSet<PositionReport>).
gdejohn
15

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

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

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

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

return new TreeSet(positionReports);
Дэниел Скотт
источник
7
Вы должны быть осторожны при этом. Вы МОЖЕТЕ потерять элементы при этом. Как и в вопросе, заданном выше, естественный компаратор элементов отличается от того, который OP хочет использовать. Таким образом, вы при первоначальном преобразовании, поскольку это набор, он может потерять некоторые элементы, которых может не иметь другой компаратор (т.е. первый компаратор может вернуть compareTo() как 0, а другой может не иметь для некоторых сравнений. Все те, где compareTo()0 потеряно, так как это набор.)
looneyGod 03
6

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

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

positionReports.removeIf(p -> p.timestamp < oldestKept);
Майкл Дэймон
источник
1

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

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

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());
    }
}
Даниэль Мора
источник
1
positionReports = positionReports.stream()
                             .filter(p -> p.getTimeStamp() >= oldestKept)
                             .collect(Collectors.toCollection(() -> new 
TreeSet<PositionReport>(Comparator.comparingLong(PositionReport::getTimestamp))));
Сирил Соян
источник