[Java] Comparable vs Comparator

sewonK·2022년 3월 31일
0

자바에서 Arrays.sort()를 호출하면 알아서 배열을 정렬하는 것처럼 보이겠지만, 사실은 Comparable의 구현에 의해 정렬된 것입니다. ComparableComparator는 모두 인터페이스로, 객체를 비교하는데 필요한 메서드를 정의하고 있습니다.

💡 객체를 비교한다?

보통 기본형 타입의 변수들의 경우, 부등호를 이용해 쉽게 비교할 수 있었습니다. 새로운 클래스 객체를 만들어 비교한다면 어떻게 비교해야 할까요?

class Person {
    private String name;
    private int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
}

이러한 Person 클래스를 만들었다고 가정해봅시다. 기본형 타입과는 달리 부등호로 비교할 수 없을 뿐만 아니라, name, age 중 어떤 것을 기준으로 비교해야 할지 모르겠습니다.

이를 위해 고안된 것이, ComparableComparator입니다. 그렇다면 두 인터페이스는 어떤 차이점이 있는지 알아봅시다.

Comparable

public interface Comparable<T> {
    public int compareTo(T o);
}

Comparable 인터페이스에서는 compareTo라는 메서드를 이용하여 객체를 비교하고 있습니다. compareTo 메서드는 파라미터가 1개로, 자기자신과 파라미터 객체를 비교하는 메서드입니다. 메서드의 반환 값은 int이며 비교하는 두 객체가 같으면 0, 비교하는 값보다 작으면 음수, 크면 양수를 반환하도록 구현해야 합니다.

Person 클래스에서 Comparable 인터페이스를 상속 받아 compareTo 메서드를 구현해봅시다.

class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public int getAge(){
        return this.age;
    }

    @Override
    public String toString() {
        return "[" + name + ": " + age + "]";
    }

    @Override
    public int compareTo(Person o) {
        // Integer.compare(a,b)
        // a value less than 0 if x < y
        // a value greater than 0 if x > y
        return Integer.compare(this.age, o.getAge());
    }
}

Comparable 인터페이스를 implements하고, compareTo 메서드를 오버라이딩 한 모습입니다. 두 객체의 값을 비교하여 같으면 0, 자기 자신이 더 크면 양수, 작으면 음수로 반환해주면 되기 때문에

return this.age - o.getAge();

와 같이 간편하게 사용할 수 있지만, underflow나 overflow의 문제가 발생할 수 있으니 직접적으로 비교하는 것이 좋습니다. 저는 Integer.compare() 메서드를 사용하여 비교하였는데요, if 문으로 비교하려 하였으나 친절한 intelliJ가 이 함수를 추천해주었습니다. Integer.compare() 메서드는 참고로 알고 있으면 좋을 것 같습니다!

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

Comparator

public interface Comparator<T> {
    int compare(T o1, T o2);
}    

Comparable은 자기 자신과 다른 객체를 비교했다면, Comparator는 서로 다른 두 객체를 비교하고 있습니다. 또 Comparable은 java.lang 패키지에 존재하여 import하지 않아도 되었다면, Comparator는 java.util 패키지에 존재하여 import해주어야 합니다.

Person 클래스를 Comparator 인터페이스를 사용하여 구현해보겠습니다.

class Person implements Comparator<Person> {
    private String name;
    private int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    public String getName(){
        return this.name;
    }

    @Override
    public String toString() {
        return "[" + name + ": " + age + "]";
    }

    @Override
    public int compare(Person o1, Person o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

이런 식으로 Person클래스 내부에 compare 메서드를 구현하여 사용할 수도 있겠지만, 비교만을 위한 객체가 만들어진다는 단점이 있습니다.

public class ComparePractice {
	public static void main(String[] args)  {
 
		Person a = new Person("mark", 24);		
		Person b = new Person("jeno", 23);		
		Person c = new Person("jisung", 21);	
		Person comp = new Person("name", 0);		// 비교만을 위해 사용할 객체
			
		//           ⋁
		int isBig = comp.compare(a, b);
        //            ⋁
		int isBig2 = comp.compare(b, c);
		//            ⋁
		int isBig3 = comp.compare(a, c);
		
	}
    //... 생략
}

이런 식으로 말이죠!

그래서 Comparator는 주로 비교 기능만 따로 구현할 수 있도록 익명 객체를 사용하거나, 비교 클래스를 만들어 사용합니다.

비교만을 위한 클래스를 만들어봅시다.

class nameSort implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        // a value less than 0 if this string is lexicographically less than the string argument;
        // a value greater than 0 if this string is lexicographically greater than the string argument.
        return o1.getName().compareTo(o2.getName());
    }
}

sort()는 Comparator를 지정해주지 않으면 객체가 구현한 Comparable을 기준으로 정렬하며, Comparator를 지정해줌으로써 기본 정렬기준(Comparable) 외에 다른 기준으로 정렬할 수 있게 됩니다.

전체 코드

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ComparePractice {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("mark", 24));
        personList.add(new Person("jeno", 23));
        personList.add(new Person("jisung", 21));

        Collections.sort(personList);
        System.out.println(personList);

        personList.sort(new nameSort());
        System.out.println(personList);
    }
}

class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public int getAge(){
        return this.age;
    }

    public String getName(){
        return this.name;
    }

    @Override
    public String toString() {
        return "[" + name + ": " + age + "]";
    }

    @Override
    public int compareTo(Person o) {
        // Integer.compare(a,b)
        // a value less than 0 if x < y;
        // a value greater than 0 if x > y
        return Integer.compare(this.age, o.getAge());
    }
}
class nameSort implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        // a value less than 0 if this string is lexicographically less than the string argument;
        // a value greater than 0 if this string is lexicographically greater than the string argument.
        return o1.getName().compareTo(o2.getName());
    }
}

출력 결과

// 나이 기준 오름차순 정렬
[[jisung: 21], [jeno: 23], [mark: 24]]

// 이름 기준 오름차순 정렬
[[jeno: 23], [jisung: 21], [mark: 24]]

📗 참고

Java의 정석 3rd Edition
자바 [JAVA] - Comparable과 Comparator

0개의 댓글