eelseungmin

[Java] Stream distinct()로 객체 타입 리스트의 중복 제거하기

by eelseungmin

배경

Java 8에서 등장한 Stream을 사용하면서 다음과 같은 장점들을 체감하게 되었다.

 

1. 불변성, 즉 스트림을 통해 연산하는 동안 참조하는 변수의 값을 변경하지 않는다.

2. 메서드 체이닝을 이용해 연산을 편리하게 조합할 수 있다.

3. 익숙해지면 스트림을 사용하지 않을 때보다 가독성이 향상된다.

 

DB에서 조회한 결과 리스트들의 중복을 스트림을 이용해 제거하는 중 겪은 문제 상황을 정리해 보았다.

 

문제 발생

distinct()만 사용한 경우

// 중복 제거 후 검색 정책에 따라 정렬
List<Place> sortedPlaces = Stream.concat(
  firstPlaces.stream(),
  secondPlaces.stream()
)
  .distinct()
  .(중략)
  .toList();

단순히 이 코드만 작성했을 땐 중복이 제대로 제거되지 않는다. 어째서일까?

 

원인 분석

Java 개발자들이 작성한 distinct()에 대한 설명을 보자.

distinct()에 대한 설명

맨 위의 문단만 보면 된다.

Object.equals() 메서드를 이용해 같은 요소임을 판단하고 중복이 제거된 요소를 스트림으로 반환한다고 되어있다.

 

String 타입이나 Integer 같은 Wrapper 클래스 타입의 경우엔 객체의 hashcode가 아닌 값 자체를 비교하도록 내부적으로 equals 메서드가 오버라이딩 되어있기 때문에 우리가 의도하던 방식으로 중복이 제거될 것이다.(즉, "string1"과 "string1"을 같은 요소로 판단한다는 뜻이다.)

 

그러나 개발자가 직접 작성한 객체 리스트에서 distinct()로 중복을 제거하려고 할 때는 경우에 따라 문제가 발생한다.

다음과 같이 Place 엔티티가 작성되어 있다고 가정하자.

@Entity
public class Place extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 100)
    private String name;

    @Column(length = 512)
    private String address;
}

equals 메서드가 오버라이딩 되어있지 않기 때문에 객체의 hashcode를 비교할 것이고, 기대했던 것과 다른 결과가 도출되어 버린다.(개발자는 "맥도날드" name을 가진 Place와 "맥도날드" name을 가진 Place가 중복이라고 판단되길 원했을 것이다.)

 

해결 방법

이제 각자의 의도에 맞게 equals 메서드를 오버라이딩하면 된다.

나는 DB 상에 저장된 Place의 기본키인 id를 바탕으로 객체의 동일성을 판별하도록 구현하였다.

@Entity
public class Place extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 100)
    private String name;

    @Column(length = 512)
    private String address;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Place place = (Place) o;
        return Objects.equals(getId(), place.getId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId());
    }
}

 

블로그의 정보

eel.log

eelseungmin

활동하기