[java] 속성 별 Java 8 구별

Java 8 Stream에서 각 객체의 속성이 다른지 확인하여 API를 사용하여 컬렉션을 필터링하려면 어떻게해야합니까?

예를 들어 Person객체 목록이 있고 같은 이름을 가진 사람을 삭제하고 싶습니다.

persons.stream().distinct();

Person객체에 기본 평등 검사를 사용 하므로 다음과 같은 것이 필요합니다.

persons.stream().distinct(p -> p.getName());

불행히도이 distinct()방법에는 그러한 과부하가 없습니다. Person클래스 내부의 동등성 검사를 수정하지 않고 간결하게 수행 할 수 있습니까?



답변

고려 distinct상태 필터 . 다음은 이전에 본 것에 대한 상태를 유지하고 주어진 요소가 처음으로 보이는지 여부를 리턴하는 술어를 리턴하는 함수입니다.

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

그럼 당신은 쓸 수 있습니다 :

persons.stream().filter(distinctByKey(Person::getName))

스트림이 정렬되고 병렬로 실행되는 경우 첫 번째 항목 대신 중복 요소 중 임의의 요소가 다음과 같이 유지됩니다.distinct() .

(이것은 본질적 으로이 질문에 대한 나의 대답 과 동일합니다 : 임의의 키에 대한 Java Lambda Stream Distinct ()? )


답변

대안은 이름을 키로 사용하여 사람을지도에 배치하는 것입니다.

persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();

이름이 중복되는 경우 보관 된 사람이 첫 번째로 확인됩니다.


답변

개인 오브젝트를 다른 클래스로 랩핑하여 개인의 이름 만 비교할 수 있습니다. 그런 다음 랩핑 된 오브젝트의 랩을 해제하여 사람 스트림을 다시 가져 오십시오. 스트림 작업은 다음과 같습니다.

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

수업 Wrapper은 다음과 같습니다.

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}


답변

를 사용하는 다른 솔루션 Set. 이상적인 솔루션은 아니지만 작동합니다.

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

또는 원래 목록을 수정할 수있는 경우 removeIf 메소드를 사용할 수 있습니다

persons.removeIf(p -> !set.add(p.getName()));


답변

커스텀 비교기와 함께 TreeSet을 사용하는 더 간단한 방법이 있습니다.

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName()))
));


답변

RxJava (매우 강력한 반응성 확장 라이브러리)를 사용할 수도 있습니다.

Observable.from(persons).distinct(Person::getName)

또는

Observable.from(persons).distinct(p -> p.getName())


답변

groupingBy수집기 를 사용할 수 있습니다 .

persons.collect(Collectors.groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

다른 스트림을 원하면 다음을 사용할 수 있습니다.

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));