Використання потоків для збору в 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))
        )
    );
}

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це тип із лише одним абстрактним методом, що означає, що він може бути ціллю лямбда-виразу, як у цій відповіді. Лямбда-вираз не повинен брати аргументів (отже, порожній список аргументів ()) і повертати колекцію з типом елемента, який відповідає типу елементів у потоці, який ви збираєте (у цьому випадку а 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

6

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

Отже, ваш код може виглядати так:

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

1

Проблема 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());
    }
}

1
positionReports = positionReports.stream()
                             .filter(p -> p.getTimeStamp() >= oldestKept)
                             .collect(Collectors.toCollection(() -> new 
TreeSet<PositionReport>(Comparator.comparingLong(PositionReport::getTimestamp))));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.