catsridingCATSRIDING|OCEANWAVES
Dev

Java List<T> 엘리먼트 특정 속성값 기반 중복 제거하기

jynn@catsriding.com
Nov 16, 2023
Published byJynn
999
Java List<T> 엘리먼트 특정 속성값 기반 중복 제거하기

Distinct List by Object Property in Java

Java Functional 인터페이스 및 Stream API를 활용하여 특정 속성을 기준으로 List<T>의 중복 엘리먼트를 제거하는 방법입니다.

Implementing

먼저, Stream API의 filter()에서 활용할 Predicate<? super T>을 구현합니다. List<T> 엘리먼트를 순회하면서 기준이 되는 속성 값이 이전에 없었는지 판별하는 로직입니다.

private static <T> Predicate<T> isUniqueKey(Function<? super T, ?> keyExtractor) {
	Map<Object, Boolean> seen = new ConcurrentHashMap<>();
	return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
  • Predicate<T>: T 타입의 인자를 받아 boolean 타입을 반환하는 함수형 인터페이스입니다.
  • Function<? super T, ?>: T 타입의 인자를 받아 R 타입으로 반환하는 함수형 인터페이스입니다.
  • ConcurrentHashMap: 멀티 쓰레드 환경에서 보다 thread-safe한 Map<K, V>의 확장 클래스입니다.
  • V putIfAbsent(K key, V value): 동일한 Key가 없는 경우 null을 반환합니다. 이를 통해 해당 Key의 중복 여부를 판별할 수 있습니다.

위에서 정의한 함수를 활용하여 중복이 제거된 새로운 List<T>를 반환하는 로직을 완성합니다.

public static List<E> distinctByKey(List<E> elements) {  
    return elements.stream()  
            .filter(isUniqueKey(E::getKey))  
            .toList();  
}

Playgrounds

테스트 코드를 통해 중복 필터링 로직이 기대한 대로 동작하는지 확인합니다.

DistinctTest.java
@Test  
void shouldDistinct() throws Exception {  
  
    //  Given  
    String phone = "010-1234-5678";  
    Cat catA = new Cat("catA", phone);  
    Cat catB = new Cat("catB", phone);  
  
    List<Cat> cats = List.of(catA, catB);  
  
    //  When  
    List<Cat> uniques = distinctByKey(cats);  
  
    //  Then  
    assertThat(cats).hasSize(2);  
    assertThat(uniques).hasSize(1);  
    assertThat(uniques.contains(catA)).isTrue();  
    assertThat(uniques.contains(catB)).isFalse();  
  
}  
  
public static List<Cat> distinctByKey(List<Cat> cats) {  
    return cats.stream()  
            .filter(isUniqueKey(Cat::getPhone))  
            .collect(Collectors.toList());  
}  
  
private static <T> Predicate<T> isUniqueKey(Function<? super T, ?> keyExtractor) {  
    Map<Object, Boolean> seen = new ConcurrentHashMap<>();  
    return t -> {  
        Object apply = keyExtractor.apply(t);  
        boolean b = seen.putIfAbsent(apply, Boolean.TRUE) == null;  
        return b;  
    };  
}  
Cat.java
@Getter  
@NoArgsConstructor  
@AllArgsConstructor  
public class Cat {  
  
    private String name;  
    private String phone;  
  
}

인스턴스의 phone 속성을 기반으로 중복된 엘리먼트가 없는 새로운 List<Cat>가 반환되었습니다. Stream API는 순차적으로 순회하기 때문에 먼저 추가된 catA 인스턴스만 필터를 통과하였습니다.

  • Java