[JAVA 더 깊게] 자바의 정렬기준 Comparator & Comparable (feat. 함수형 인터페이스)
Arrays.sort() , 스트림 sorted() 등등을 사용하다보면 심심치 않게 파라미터에 Comparator가 있는걸 알수있다.
정렬과 같은 요소들 사이에 비교가 필요한 곳에 등장한다.
Comparator , Comparable 이름도 비슷하고 하는 일도 비슷해 보인다. 이들은 무엇이고 어떻게 활용되는지 알아보자.
Comparator & Comparable 인터페이스
public interface Comparator {
int compare(Object o1, Objcet o2);
boolean equals(Object obj) //필요에 따라 재정의
//기타 default methods
}
public interface Comparable {
public int compareTo(Object o);
//기타 defualt methods
}
Comparator , Comparable의 실제 소스는 위와같다.
둘다 인터페이스이다.
compare와 compareTo를 정의함으로써 정렬기준을 정의한다.
자바가 제공하는 클래스, Wrapper class , String , File 등 인스턴스끼리 비교가 가능한 클래스들은 모두 Comparable을 구현하고있다.
자바에서 제공하는 비교가능한 클래스들은 모두 Comparable을 구현하고있다
기본으로 오름차순으로 구현되어있음
ex) Integer class
public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc { //... 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); } }Comparable 을 구현하고 compareTo 추상 메서드를 재정의 하고있다.
클래스들은 Comparator 가 아니라 Comparable 인터페이스를 구현하고 있다.
Comparator 를 알아보기 전 우선 배열을 정렬할때 사용하는 Arrays.sort()의 동작 과정을 보자.
//main.java
Integer[] list = {1,3,2,4};
Arrays.sort(list); //1 2 3 4
list의 각 요소는 Integer이다.
Integer는 Comparable을 구현하고 compareTo를 재정의 하고있다.
이 compareTo를 정렬기준으로 배열을 정렬한다.
이를 기본 정렬 기준 이라고 한다.
그럼 Comparator 인터페이스는 뭘까?
Comparator & Comparable의 차이
인터페이스라는게 어떤 기능을 체계화 하기 위해서 존재한다. Comparator & Comparable도 같은 이유이다.
이름만 다르지 능은 동일하지만, 그 목적이 구분되어있는것
Comparator & Comparable의 목적
- Comparable은 기본 정렬 기준을 정의할때 사용
- Comparator는 정렬시 기본 정렬기준이 아닌 사용자가 정의한 정렬기준을 적용하고싶을때 사용한다.
예제로 보면 이해가 될것이다.
''Comparable''은 클래스에 구현하며 기본 정렬기준을 정의할때 사용한다.
class Person implements Comparable<Person>{ //해당 클래스의 기본정렬기준을 정의하기 위해 Comparable 인터페이스를 구현한다.
private int age;
public Person(int age) {
this.age = age;
}
....
public int getAge(){
return age;
}
@Override
public int compareTo(Person o) { //기본 정렬 기준 정의
int thisAge = this.age;
int anotherAge = o.age;
return (thisAge < anotherAge ? -1 : (thisAge == anotherAge ? 0 : 1)); //오름차순
}
}
이렇게 클래스에 Comparable 인터페이스를 구현해서 기본 정렬 기준을 정의한다.
사용자가 정의한 클래스에 기본 정렬기준을 정의
아까 봤듯이 자바가 제공하는 비교가능한 클래스 Wrapper class나 String들은 java에서 이미 오름차순으로 구현을 해놨다.
Comparator는 예제를 통해 알아보자
Arrays.sort를 가지고 Comparator가 뭔지 살펴보자.
방금 정의한 Person 객체를 사용해보자.
객체는 컬랙션을 통해 다루지만 Arrays.sort를 예시로 들기위해 그냥 배열로 만들었다.
1. Arrays.sort(T[] a)
- Comparator 인자가 없는경우 객체의 기본정렬기준으로 정렬한다.
Person의 compareTo()로 요소끼리 상호 비교해서 정렬한다.
Person[] a = {new Person(20),new Person(10)}; //객체 배열
Arrays.sort(a); //해당 객체에 정의된 기본 정렬 기준을 가지고 정렬한다.
for(Person i : a){
System.out.println(i.getAge()); //10 20
}
Comparable은 이렇게 객체에 기본 정렬기준을 가지고 정렬할때 사용되는 것이다.
그럼 정렬할때 기본 정렬 기준이 아니라 새로운 기준으로 정렬하고 싶을때는?
여기서 Comparator 인터페이스가 사용된다.
2. Arrays.sort(T[] a, Comparator<? super T> c)
- 이렇게 sort에 2번째 인자로 Comparator가 쓰이는경우가 있다.
Person[] a = {new Person(10),new Person(20)};
Arrays.sort(a,(p1,p2)-> p1.compareTo(p2)*-1 ); //새로운 정렬기준: -1을 곱해줌으로써 내림차순으로 바꿔버림 :람다를 이용
for(Person i : a){
System.out.println(i.getAge()); //20 10
}
이경우 위와 다르게 20 10 으로 내림차순으로 바뀌는것을 볼수있다.
Comparator는 객체에 정의된 Comparable기본 정렬기준이 아닌 새로운 정렬기준을 적용하고싶을때 사용되는것이다.
람다를 이용한 이유는 아래서 설명할것이다.
3. String 에서의 Comparator
- String class는 여러가지 정렬기준을 상수로 제공한다.
- 기본은 사전순이다.
Arrays.sort(a,String.CASE_INSENSITIVE_ORDER); //대소문자 구분없이 사전순 정렬
Comparator를 이용하면 다른 정렬기준으로 손쉽게 정렬이 가능하다.
Comparator는 다양한 정렬 메서드의 인자로써 사용되며, 기본정렬기준이 아닌 새로운 정렬기준을 적용하고싶을때 정의하면 된다.
람다와 Comparator
Comparator는 추상메서드를 compare() 하나만 가지고있다.
정확히는 equals도 있지만 equals는 Object의 public 함수이기때문에 재정의가 강제되지 않는다.
이처럼 재정의가 강제되는 추상메서드를 1개만 가지고있는 인터페이스를 함수형 인터페이스라고한다.
https://onlyforus-blog.tistory.com/168?category=985817
이전 게시물에서 정리했었다.
함수형 인터페이스는 람다식으로 변환이 가능하다.
그래서 Arrays.sort(T[] a, Comparator<? super T> c) 이 예제에서 람다를 사용해 정렬기준을 제시할수있는것이다.
Person[] a = {new Person(10),new Person(20)};
Arrays.sort(a,(p1,p2)-> p1.compareTo(p2)*-1 ); //람다식
for(Person i : a){
System.out.println(i.getAge()); //20 10
}
스트림과 Comparator
스트림에 중간연산중 sorted()라는 메서드가 존재한다.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
이것도 Arrays.sort() 와 비슷하다.
Stream<String> stream = Stream.of("a","c","C","b");
stream.sorted() //사전순 -> C a b c
stream.sorted(String.CASE_INSENSITIVE_ORDER) //대소문자 구분 X -> a b C c
람다로도 가능하지만 String은 여러 정렬기준을 상수형태로 제공한다.
Comparator 인자를 넘겨주면 해당 정렬 기준으로 정렬하고, 넘겨주지 않으면 해당 요소의 Comparable 기본정렬기준으로 정렬한다.
스트림으로 데이터 군집을 정렬할때 Comparator 를 이용해 다양한 방법으로 정렬할수있다.
정리
둘은 인자를 비교하는 기준을 정의한다는것은 동일하지만 사용 목적과 위치가 다름
Comparable
- 클래스에 구현해서 기본 정렬 기준으로 사용
- 다양한 정렬 메서드에 Comparator인자가 없을때 이 기본 정렬 기준으로 요소를 비교해서 정렬한다.
Comparator
- 정렬 메서드 사용시 기본정렬기준이 아닌 다른 정렬기준으로 정렬하고싶을때 사용
- 다양한 정렬 메서드에 Comparator인자를 넘겨줌으로써 사용가능
- 함수형 인터페이스로 람다로 인자 넘길수있음
Comparator를 이용하면 정렬시 그때그때 필요한 정렬기준을 사용할수있다.
'개발 일지 > JAVA' 카테고리의 다른 글
| 오토박싱 & 언박싱 (0) | 2022.03.07 |
|---|---|
| [JAVA 더 깊게] Stream(feat. 함수형 인터페이스 & Comparator) (0) | 2022.03.07 |
| JVM 메모리 구조 (0) | 2022.03.04 |
| [JAVA 더 깊게] Lamda(feat.컬랙션프레임웍) (0) | 2022.03.03 |
| [JAVA 더 깊게] JAVA One Paper (0) | 2022.01.10 |