1. 개요

Java 개발자로서 우리는 종종 컬렉션으로 그룹화 된 요소를 정렬해야합니다. Java를 사용하면 모든 유형의 데이터로 다양한 정렬 알고리즘을 구현할 수 있습니다 .

예를 들어, 알파벳 순서, 알파벳 역순 또는 길이를 기준으로 문자열을 정렬 할 수 있습니다.

이 자습서에서는 정렬을 가능하게 하는 Comparable 인터페이스와 compareTo 메서드를 살펴 보겠습니다 . 핵심 및 사용자 지정 클래스의 개체를 포함하는 컬렉션 정렬을 살펴 보겠습니다.

또한 compareTo 를 올바르게 구현하기위한 규칙과 피해야하는 깨진 패턴에 대해서도 언급 합니다.

2. 비교 가능한 인터페이스

에 Comparable 인터페이스 강요하며이 구현을하는 각 클래스의 객체에 주문 .

compareTo와는 에 의해 정의되는 유일한 방법이다 필적 인터페이스. 종종 자연 비교 방법이라고합니다.

2.1. compareTo 구현

의 compareTo 메소드 의 파라미터로서 전송 오브젝트와 현재의 오브젝트를 비교한다 .

구현할 때 메서드가 다음을 반환하는지 확인해야합니다.

  • 현재 개체가 매개 변수 개체보다 큰 경우 양의 정수
  • 현재 개체가 매개 변수 개체보다 작은 경우 음의 정수
  • 0 (현재 개체가 매개 변수 개체와 같은 경우)

수학에서는 이것을 부호 또는 부호 함수라고 부릅니다.

Signum 함수

2.2. 구현 예

코어 Integer 클래스 에서 compareTo 메서드가 어떻게 구현 되는지 살펴 보겠습니다 .

@Override
public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}

public static int compare (int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

2.3. 깨진 빼기 패턴

대신 영리한 빼기 한 줄을 사용할 수 있다고 주장 할 수 있습니다.

@Override
public int compareTo(BankAccount anotherAccount) {
    return this.balance - anotherAccount.balance;
}

양수 계정 잔액이 음수 잔액보다 클 것으로 예상되는 예를 살펴 보겠습니다.

BankAccount accountOne = new BankAccount(1900000000);
BankAccount accountTwo = new BankAccount(-2000000000);
int comparison = accountOne.compareTo(accountTwo);
assertThat(comparison).isNegative();

그러나 정수는 차이를 저장할만큼 크지 않아 잘못된 결과를 제공합니다. 확실히,이 패턴은 가능한 정수 오버플로로 인해 깨져서 피해야 합니다.

올바른 해결책은 빼기 대신 비교를 사용하는 것입니다. 핵심 Integer 클래스 에서 올바른 구현을 재사용 할 수도 있습니다 .

@Override
public int compareTo(BankAccount anotherAccount) {
    return Integer.compare(this.balance, anotherAccount.balance);
}

2.4. 구현 규칙

compareTo 메서드 를 올바르게 구현 하려면 다음 수학 규칙을 준수해야합니다.

  • sgn (x.compareTo (y)) == -sgn (y.compareTo (x))
  • (x.compareTo (Y)> 0 && y.compareTo (z)> 0) 을 의미  > 0 x.compareTo (Z)를
  • x.compareTo (y) == 0  은  sgn (x.compareTo (z)) == sgn (y.compareTo (z))를 의미합니다.

또한 강하게하는,하지만 필요하지, 추천  킵 의 compareTo (가)와 일관된 구현을 동일 메소드 구현을 :

  • x.compareTo (e2) == 0x.equals (y) 와 동일한 부울 값을 가져야합니다.

이렇게하면 정렬 된 세트와 정렬 된 맵에서 객체를 안전하게 사용할 수 있습니다.

2.5. 같음 과의 일관성

compareToequals 구현이 일관성이 없을 때 어떤 일이 발생할 수 있는지 살펴 보겠습니다 .

이 예에서 compareTo 메서드는 득점 한 골을 확인하고 equals 메서드는 플레이어 이름을 확인합니다.

@Override
public int compareTo(FootballPlayer anotherPlayer) {
    return this.goalsScored - anotherPlayer.goalsScored;
}

@Override
public boolean equals(Object object) {
    if (this == object)
        return true;
    if (object == null || getClass() != object.getClass())
        return false;
    FootballPlayer player = (FootballPlayer) object;
    return name.equals(player.name);
}

이로 인해 정렬 된 세트 또는 정렬 된 맵에서이 클래스를 사용할 때 예기치 않은 동작이 발생할 수 있습니다.

FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 800);

TreeSet<FootballPlayer> set = new TreeSet<>();
set.add(messi);
set.add(ronaldo);

assertThat(set).hasSize(1);
assertThat(set).doesNotContain(ronaldo);

정렬 된 집합은 equals 메서드가 아닌 compareTo를 사용하여 모든 요소 비교를 수행합니다 . 따라서 두 플레이어는 관점에서 동등하게 보이며 두 번째 플레이어를 추가하지 않습니다.

3. 컬렉션 정렬

Comparable 인터페이스 의 주요 목적은 컬렉션 또는 배열로 그룹화 된 요소를 자연스럽게 정렬수 있도록하는 것 입니다.

Java 유틸리티 메소드 Collections.sort 또는 Arrays.sort를 사용하여 Comparable 을 구현하는 모든 객체를 정렬 할 수 있습니다 .

3.1. 핵심 자바 클래스

String , Integer 또는 Double 과 같은 대부분의 핵심 Java 클래스 는 이미 Comparable 인터페이스를 구현합니다 .

따라서 기존의 자연스러운 정렬 구현을 재사용 할 수 있으므로 정렬이 매우 간단합니다.

자연적인 순서로 숫자를 정렬하면 오름차순이됩니다.

int[] numbers = new int[] {5, 3, 9, 11, 1, 7};
Arrays.sort(numbers);
assertThat(numbers).containsExactly(1, 3, 5, 7, 9, 11);

반면에 문자열을 자연스럽게 정렬하면 알파벳 순서가됩니다.

String[] players = new String[] {"ronaldo",  "modric", "ramos", "messi"};
Arrays.sort(players);
assertThat(players).containsExactly("messi", "modric", "ramos", "ronaldo");

3.2. 커스텀 클래스

반대로 모든 사용자 정의 클래스를 정렬 할 수 있으려면 Comparable 인터페이스 를 수동으로 구현해야합니다 .

Java 컴파일러는 Comparable을 구현하지 않는 개체 컬렉션을 정렬하려고하면 오류를 발생시킵니다 .

배열에 대해 동일하게 시도하면 컴파일 중에 실패하지 않습니다. 그러나 클래스 캐스트 런타임 예외가 발생합니다.

HandballPlayer duvnjak = new HandballPlayer("Duvnjak", 197);
HandballPlayer hansen = new HandballPlayer("Hansen", 196);
HandballPlayer[] players = new HandballPlayer[] {duvnjak, hansen};
assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> Arrays.sort(players));

3.3. TreeMapTreeSet

TreeMap TreeSet 해당 요소의 자동 정렬 지원 하는 Java Collections Framework의 두 가지 구현입니다.

정렬 된 맵 또는 정렬 된 세트의 요소로 Comparable 인터페이스 를 구현하는 객체를 사용할 수 있습니다 .

득점 한 골 수를 기준으로 플레이어를 비교하는 사용자 지정 클래스의 예를 살펴 보겠습니다.

@Override
public int compareTo(FootballPlayer anotherPlayer) {
    return Integer.compare(this.goalsScored, anotherPlayer.goalsScored);
}

이 예에서 키는 compareTo 구현에 정의 된 기준에 따라 자동으로 정렬됩니다 .

FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 900);
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer modric = new FootballPlayer("modric", 100);

Map<FootballPlayer, String> players = new TreeMap<>();
players.put(ronaldo, "forward");
players.put(messi, "forward");
players.put(modric, "midfielder");

assertThat(players.keySet()).containsExactly(modric, messi, ronaldo);

4. 비교기 대안

자연스러운 정렬 외에도 Java를 사용하면 유연한 방식으로 특정 정렬 논리를 정의 할 수 있습니다.

비교기 인터페이스는 우리가 정렬 된 객체에서 분리 된 여러 다른 비교 전략을 수 있습니다 :

FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 900);
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer modric = new FootballPlayer("Modric", 100);

List<FootballPlayer> players = Arrays.asList(ronaldo, messi, modric);
Comparator<FootballPlayer> nameComparator = Comparator.comparing(FootballPlayer::getName);
Collections.sort(players, nameComparator);

assertThat(players).containsExactly(messi, modric, ronaldo);

일반적으로 정렬하려는 객체의 소스 코드를 수정하고 싶지 않거나 수정할 수없는 경우에도 좋은 선택입니다.

5. 결론

이 글에서, 우리는 우리가 할 수있는 방법으로보고 사용 Comparable를 자연 정렬 알고리즘을 정의하는 인터페이스를 우리의 자바 클래스. 일반적인 깨진 패턴을 살펴보고 compareTo 메서드 를 올바르게 구현하는 방법을 정의했습니다 .

또한 핵심 및 사용자 지정 클래스를 모두 포함하는 정렬 컬렉션을 탐색했습니다. 다음으로, 정렬 된 세트와 정렬 된 맵에 사용되는 클래스 에서 compareTo 메서드 의 구현을 고려했습니다 .

마지막으로 Comparator 인터페이스를 대신 사용해야하는 몇 가지 사용 사례를 살펴 보았습니다 .

항상 그렇듯이 소스 코드는 GitHub에서 사용할 수 있습니다 .