development

속성 별 Java 8 구별

big-blog 2020. 2. 25. 22:47
반응형

속성 별 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(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())

Eclipse Collections 에서 distinct(HashingStrategy)메소드를 사용할 수 있습니다 .

List<Person> persons = ...;
MutableList<Person> distinct =
    ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));

personsEclipse Collections 인터페이스를 구현하기 위해 리팩토링 할 수있는 경우 목록에서 직접 메소드를 호출 할 수 있습니다.

MutableList<Person> persons = ...;
MutableList<Person> distinct =
    persons.distinct(HashingStrategies.fromFunction(Person::getName));

HashingStrategy 는 단순히 equals 및 hashcode의 사용자 정의 구현을 정의 할 수있는 전략 인터페이스입니다.

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

참고 : 저는 Eclipse Collections의 커미터입니다.


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)));

StreamEx 라이브러리 를 사용할 수 있습니다 :

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()

가능 하다면 Vavr을 사용하는 것이 좋습니다 . 이 라이브러리를 사용하면 다음을 수행 할 수 있습니다.

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection

Stuart Marks의 답변을 확장하면 병렬 스트림이 필요없는 경우 짧은 시간과 동시지도없이 수행 할 수 있습니다.

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

그런 다음 전화 :

persons.stream().filter(distinctByKey(p -> p.getName());

나는 일반 버전을 만들었습니다.

private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
    return Collectors.collectingAndThen(
            toMap(
                    keyExtractor,
                    t -> t,
                    (t1, t2) -> t1
            ),
            (Map<R, T> map) -> map.values().stream()
    );
}

예 :

Stream.of(new Person("Jean"), 
          new Person("Jean"),
          new Person("Paul")
)
    .filter(...)
    .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
    .map(...)
    .collect(toList())

Set<YourPropertyType> set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);

Saeed Zarinfam과 비슷한 접근법이지만 Java 8 스타일이 더 많습니다.)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());

이를 지원하는 또 다른 라이브러리는 jOOλ 이며 그 Seq.distinct(Function<T,U>)방법은 다음 같습니다.

Seq.seq(persons).distinct(Person::getName).toList();

그러나 실제로는 허용되는 답변 과 동일한 기능을 수행합니다 .


고유 한 객체 목록은 다음을 사용하여 찾을 수 있습니다.

 List distinctPersons = persons.stream()
                    .collect(Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
                            ArrayList::new));

이 기능을 구현하는 가장 쉬운 방법은 정렬 기능을 Comparator사용하여 요소의 속성을 사용하여 만들 수 있는 옵션 이미 제공하는 것 입니다. 그런 다음 Predicate정렬 된 스트림의 모든 동일한 요소가 인접한다는 사실을 사용하는 statefull 을 사용 하여 수행 할 수있는 중복을 필터링해야합니다 .

Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* more stream operations here */;

물론 statefull Predicate은 스레드로부터 안전하지 않지만 필요한 경우이 논리를 a로 이동 Collector하여 스트림 사용시 스레드 안전을 처리하도록 할 수 있습니다 Collector. 이것은 당신이 당신의 질문에서 우리에게 말하지 않은 독특한 요소의 흐름으로 무엇을하고 싶은지에 달려 있습니다.


이것에 대한 나의 접근 방식은 동일한 속성을 가진 모든 객체를 그룹화 한 다음 그룹을 1의 크기로 줄인 다음 마침내로 수집하는 것 List입니다.

  List<YourPersonClass> listWithDistinctPersons =   persons.stream()
            //operators to remove duplicates based on person name
            .collect(Collectors.groupingBy(p -> p.getName()))
            .values()
            .stream()
            //cut short the groups to size of 1
            .flatMap(group -> group.stream().limit(1))
            //collect distinct users as list
            .collect(Collectors.toList());

@ josketres의 답변을 바탕으로 일반적인 유틸리티 방법을 만들었습니다.

Collector 를 작성하여 Java를보다 친숙하게 만들 수 있습니다.

public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
    return input.stream()
            .collect(toCollection(() -> new TreeSet<>(comparer)));
}


@Test
public void removeDuplicatesWithDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(7), new C(42), new C(42));
    Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
    assertEquals(2, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 7));
    assertTrue(result.stream().anyMatch(c -> c.value == 42));
}

@Test
public void removeDuplicatesWithoutDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(1), new C(2), new C(3));
    Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
    assertEquals(3, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 1));
    assertTrue(result.stream().anyMatch(c -> c.value == 2));
    assertTrue(result.stream().anyMatch(c -> c.value == 3));
}

private class C {
    public final int value;

    private C(int value) {
        this.value = value;
    }
}

아마도 누군가에게 유용 할 것입니다. 나는 또 다른 요구 사항이 있었다. A타사 의 객체 목록을 사용 하면 동일한 A.b필드 가 동일한 모든 객체 를 제거합니다 A.id( 목록에서 A동일한 여러 객체 A.id). Tagir Valeev의 스트림 파티션 응답은 나에게 custom 을 반환 하도록 영감을주었습니다 . 간단 하게 나머지를 할 것입니다.CollectorMap<A.id, List<A>>flatMap

 public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) HashMap::new,
            (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
            (left, right) -> {
                left.putAll(right);
                return left;
            }, map -> new ArrayList<>(map.values()),
            Collector.Characteristics.UNORDERED)); }

내 경우에는 이전 요소를 제어해야했습니다. 그런 다음 이전 요소가 현재 요소와 다른지 여부를 제어 하는 상태 저장 술어 를 작성 했습니다.

public List<Log> fetchLogById(Long id) {
    return this.findLogById(id).stream()
        .filter(new LogPredicate())
        .collect(Collectors.toList());
}

public class LogPredicate implements Predicate<Log> {

    private Log previous;

    public boolean test(Log atual) {
        boolean isDifferent = previouws == null || verifyIfDifferentLog(current, previous);

        if (isDifferent) {
            previous = current;
        }
        return isDifferent;
    }

    private boolean verifyIfDifferentLog(Log current, Log previous) {
        return !current.getId().equals(previous.getId());
    }

}

작성할 수있는 가장 간단한 코드 :

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());

다음 사람 목록을 원한다면 간단한 방법이 될 것입니다.

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

당신은 별개의 또는 고유 찾으려는 경우 또한, 이름 목록 , 없는 사람을 , 당신은뿐만 아니라이 방법에 따라 이용 할 수 있습니다.

방법 1 : 사용 distinct

persons.stream().map(x->x.getName()).distinct.collect(Collectors.toList());

방법 2 : 사용 HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));

참고 URL : https://stackoverflow.com/questions/23699371/java-8-distinct-by-property



반응형