속성 별 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));
persons
Eclipse 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 을 반환 하도록 영감을주었습니다 . 간단 하게 나머지를 할 것입니다.Collector
Map<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
'development' 카테고리의 다른 글
여러 열에서 어떻게 DISTINCT를 선택합니까? (0) | 2020.02.25 |
---|---|
tbody 내부에 세로 스크롤이있는 100 % 너비의 HTML 테이블 (0) | 2020.02.25 |
'sudo : tty present 및 askpass program specified'오류 수정 방법 (0) | 2020.02.25 |
.NET / C #-char []를 문자열로 변환 (0) | 2020.02.25 |
HTML 버튼 또는 JavaScript를 클릭 할 때 파일 다운로드를 트리거하는 방법 (0) | 2020.02.25 |