JAVA에서 객체의 정렬을 위해서 사용되는 인터페이스로 2가지 모두 같은 목적으로 사용되나 구현 방식과 적용 범위의 차이가 있다.
Comparable 인터페이스는 객체가 자연적인 순서(natural order)를 갖도록 하는데 사용된다.
여기서 말하는 자연적인 순서는 String이나 다른 Integer와 같은 타입 래퍼 클래스들에 이미 구현되어 있는 정렬 방법을 말한다.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
public final class Integer extends Number implements Comparable<Integer>
위는 각각 String와 Integer의 선언부로 모두 Comparable을 구현하고 있는 것을 확인 할 수 있다.
Comparable을 구현한 클래스는 compareTo() 메소드를 오버라이딩하여 다른 객체와 비교 결과를 int(정수)타입의 값으로 반환 해야 한다.
compareTo() 메소드는 비교 대상 객체보다 작으면 음수, 같으면 0, 크면 양수를 반환한다.
Integer 값 5를 가지고 인수로 받은 값과 비교한 결과 값을 return 하게 되며 비교 방식은 우리에게 친숙한 값의 비교를 통해서 이루어진다
Integer integer = 5;
int result1 = integer.compareTo(1);
int result2 = integer.compareTo(5);
int result3 = integer.compareTo(10);
System.out.println(result1);
// 출력 : 1
System.out.println(result2);
// 출력 : 0
System.out.println(result3);
// 출력 : -1
String의 값의 정렬은 알파벳 순서대로 이루어 지며 'a'와 'b'를 비교하면 'a'가 순서상 앞에 있기 때문에 양수를 return하는 방식이다.
사실 이 순서는 문자를 비교할 때는 ASCII로 변환하여 비교하기 때문이다. 이를 기억해두면 비교 하는 방식이 'a'는 97로 변환되며 'b'는 98로 변환된다. 즉, 98 > 97 같은 비교 방식이다.
String string = "b";
int result1 = string.compareTo("a");
int result2 = string.compareTo("b");
int result3 = string.compareTo("c");
System.out.println(result1);
// 출력 : 1
System.out.println(result2);
// 출력 : 0
System.out.println(result3);
// 출력 : -1
Person 클래스를 Comparable을 구현하면 class 내부에서 값의 비교를 할수 있게 된다.
만약, Comparable을 구현하지 않은 Person이라면 age와 money중 2가지 중에 어떤 것이 정렬의 기준이 되어야 할지 알기 어렵게 된다. 하지만 아래처럼 compareTo를 age로 지정하게 된다면 앞서 '자연적인 순서'를 지정하는 것과 같다
'즉, Person 클래스의 자연적인 순서는 age를 기준으로 한다.'를 명시한다고 생각하면 좋다.
class Person implements Comparable<Person> {
private final int age;
private final int money;
Person(int age, int money) {
this.age = age;
this.money = money;
}
public int getAge() {
return age;
}
public int getMoney() {
return money;
}
@Override
public int compareTo(Person o) {
return this.age > o.getAge() ? 1 : this.age == o.getAge() ? 0 : -1;
}
}
public static void classCompare() {
Person person1 = new Person(20, 1000);
Person person2 = new Person(40, 2000);
System.out.println(person1.compareTo(person2));
// 출력 : class compareTo : -1
}
Comparator 인터페이스는 객체의 정렬 순서를 임의로 지정하기 위해 사용된다.
Comparable과는 다르게 Comparator는 정렬 기준이 되는 클래스와 별도로 정의하여 전달하게 된다.
즉, 이미 Comparable을 구현한 클래스의 정렬 순서를 변경하거나, Comparable을 구현하지 않는 클래스의 정렬 기준을 정의할 때 사용하면 된다.
Comparator를 구현한 클래스는 대표적으로 compare() 메소드를 오버라이딩하여 두 객체를 비교하는 로직을 구현할 수 있다. 두 객체를 인수로 받아서 원하는 방식으로 비교하여 정수(int)값을 반환하면 된다.
ArrayList에 값을 차례대로 추가하는데 값은 정렬이 되어 있지 않다. 값의 정렬을 위하여 Collections.sort 메소드를 사용하는데 각각 인자로 list와 값의 정렬 방식을 Comparator의 compare를 구현하여 전달하면 값을 정렬할 수 있다.
public static void integerComparator() {
// Collections를 활용
List<Integer> arrays = new ArrayList<>();
arrays.add(40);
arrays.add(10);
arrays.add(30);
arrays.add(20);
arrays.add(0);
arrays.add(50);
Collections.sort(arrays, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? 1 : o1 == o2 ? 0 : -1;
}
});
System.out.println(arrays);
// 출력 : [0, 10, 20, 30, 40, 50]
}
String 배열을 만들면서 문자열을 미리 만들며서 정렬 되지 않은 문자열의 배열을 만들었다. 여기에선 Arrays.sort를 활용하여 각각 인자로 array와 정렬 방식을 구현한 Comparator를 전달하는데 여기에서는 역순으로 하기 위해 Comparator에 미리 구현되어 있는 reverseOrder()를 전달하여 배열의 값을 역순으로 정렬하였다.
public static void stringComparator() {
// Arrays를 활용
String[] stringArr = new String[]{"a", "b", "q", "u", "s", "c"};
Arrays.sort(stringArr, Comparator.reverseOrder());
System.out.println(Arrays.toString(stringArr));
// 출력 : [u, s, q, c, b, a]
}
이렇게 오름차순이 아니라 쉽게 내림차순으로 정렬하는 'reverseOrder'와 같은 메소드를 활용할 수도 있다
ArrayList를 만들면서 Human을 생성하여 추가하면서 Collections.sort에 인자로 정렬을 원하는 list와 정렬하는 방식을 지정한 Comparator의 compare를 구현하는데 Human의 getAge 메소드를 이용하여 값의 비교로 정렬 방식을 구현하여 전달하였다.
class Human {
private final int age;
int getAge() {
return this.age;
}
Human(int age) {
this.age = age;
}
@Override
public String toString() {
return "human age : " + this.age;
}
}
public static void classComparator() {
// 람다식을 활용하여 Human.getAge 메소드로 age값을 가져와 비교한다
Comparator<Human> ageComparator = new Comparator<Human>() {
@Override
public int compare(Human o1, Human o2) {
return o1.getAge() < o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
}
};
// Collections를 활용
List<Human> humanList = new ArrayList<>();
humanList.add(new Human(10));
humanList.add(new Human(50));
humanList.add(new Human(30));
humanList.add(new Human(20));
humanList.add(new Human(40));
Collections.sort(humanList, ageComparator);
System.out.println(humanList);
// 출력 : [human age : 50, human age : 40, human age : 30, human age : 20, human age : 10]
}