스트림을 사용하여 사용자 정의 비교기로 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 2015-08-27

35
@xtrakBandit 위로 다음 - 당신은 비교기 (자연 정렬)를 지정할 필요가없는 경우 다시 - 당신은 매우 간결 그것을 만들 수 있습니다 :.collect(Collectors.toCollection(TreeSet::new));
여호수아 골드버그

이 오류가 발생했습니다.toCollection in class Collectors cannot be applied to given types
Bahadir Tasdemir

@BahadirTasdemir 코드가 작동합니다. 당신이 단 하나 개의 인수를 전달하고 있는지 확인 Collectors::toCollectionA : Supplier반환합니다 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

6

스트림을 사용하지 않고도 Collection에 대한 메소드가 있습니다 default boolean removeIf(Predicate<? super E> filter). Javadoc을 참조하십시오 .

따라서 코드는 다음과 같이 보일 수 있습니다.

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

1

TreeSet의 문제점은 항목을 정렬하기 위해 원하는 비교기가 항목을 세트에 삽입 할 때 중복을 감지하는데도 사용된다는 것입니다. 따라서 비교기 기능이 두 항목에 대해 0이면 중복으로 간주하여 잘못 버립니다.

중복 검색은 항목의 별도의 올바른 hashCode 메서드로 수행해야합니다. 모든 속성 (예제의 ID 및 이름)을 고려하여 hashCode로 중복을 방지하고 항목을 가져올 때 간단한 정렬 된 목록을 반환하기 위해 간단한 HashSet을 사용하는 것을 선호합니다 (예제에서는 이름으로 만 정렬).

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))));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.