java, 비교와 정렬 - Comparable, Comparator, Arrays.sort 외

자바에서 비교를 위해

  • 기본타입의 경우 단순하게 비교가 되지만 객체의 경우 비교하기가 어렵다.
  • 자바에서는 Comparable 와 Comparator를 통해 비교를 위한 명확하고 확장 가능한 인터페이스를 제공한다.
if(10>20) {} // 가능
if(userA > userB){} // 불가능

Comparator

  • Comparator는 기본 타입에 대한 래퍼 클래스의 비교가 가능하지만 특정 객체에 특화된 비교를 제공한다.
  • 다음과 같이 User라는 클래스가 있고 그것을 다음과 같이 Comparator를 작성하여 비교해봤다.

  • User
@Data
public class User {
    private final String name;
    private final int money;
    private final int age;
}
  • money 와 age 를 기준으로 비교한다.
  • Comparator 을 사용하여 특정 객체에 특화된 비교가 가능하다.
  • 비교는 좌항과 우항의 차이를 통해 비교한다. 양수가 나올 경우 좌항이 크다고 보며 음수가 나올 경우 우항이 크다고 본다.
Comparator<User> compareWithMoney = Comparator.comparingInt(User::getMoney);
Comparator<User> compareWithAge   = Comparator.comparingInt(User::getAge);

User older = new User("kim", 10, 20);
User richer = new User("lee", 30, 10);

compareWithMoney.compare(richer, older); // 양수
compareWithMoney.compare(older, richer); // 음수

compareWithAge.compare(older, richer); // 양수
compareWithAge.compare(richer, older); // 음수

Comparable

  • Comparable을 구현하면 Comparator 등 별도로 비교를 위한 코드를 작성하지 않아도 된다.
  • int compareTo(Object o); 을 오버라이딩 해야 한다.
  • Assertions#isSorted 가 Comparable 을 기준으로 한다.

  • UserOverFlow
public class UserOverFlow extends User implements Comparable<User>{
    public UserOverFlow(String name, int money, int age) {
        super(name, money, age);
    }

    @Override
    public int compareTo(User o) {
        return getAge() - o.getAge();
    }
}
  • 아래와 같이 Comparator가 없어도 정렬 가능하다.
UserOverFlow older = new UserOverFlow("old", 10, 20);
UserOverFlow younger = new UserOverFlow("young", 30, 10);

// 정상적으로 정렬 됨
List<UserOverFlow> list = Arrays.asList(younger, older);
Collections.sort(list);

System.out.println(list);
  • 하지만 compareTo을 getAge() - o.getAge()getAge() - o.getAge()로 구현하였다. 이는 오버플로우로부터 안전하지 않다.
  • 게다가 테스트를 위한 org.assertj.core.api.Assertions의 메서드가 이 문제를 잡아내지 못한다!
UserOverFlow older = new UserOverFlow("old", 10, Integer.MAX_VALUE);
UserOverFlow younger = new UserOverFlow("young", 30, -1); // 나이가 음수일 수는 없지만 ㅠ 

// 우항이 커서 음수를 리턴. 비정상적으로 동작.
older.compareTo(younger); // 음수

// 정렬 결과도 비정상
List<UserOverFlow> list = Arrays.asList(younger, older);
Collections.sort(list);

// true를 반환! 헐! 믿었던 junit이 동작하지 않음! Comparable 을 잘 구현해야 한다!
Assertions.assertThat(list).isSorted();
  • 아래와 같이 변경하였고 정상 동작한다.
@Override
public int compareTo(User u) {
    return Integer.compare(getAge(), u.getAge());
}