프로그래밍공부(Programming Study)/자바(JAVA)

(JAVA/자바)compareTo와 Comparator 그리고 정렬

Chaany 2022. 2. 20.
728x90

자바에서 primitive type(기본형) 중 OO.Compare이 있는 경우를 제외하고 객체 비교를 할 때 compareTo 또는 Comparator를 이용한다.

 

그중 Comparator의 경우 (특정 객체).sort(), Collections.sort함수 또는 Arrays.sort함수를 쓸 때 파라미터로 정렬기준(내림차순, 오름차순)으로 많이 활용하므로 "무조건" 익숙해지면 좋은 인터페이스이다.

 

1. Comparator에 관하여

Comparator 인터페이스의 경우  f3을 누르거나 ctrl + 마우스 좌클릭을 할 경우 아래와 같은 내용을 확인할 수 있다.

API 뜯어보기를 일상화하자!

인터페이스이므로 결국 해당 compare함수를 재정의해서 쓰라는 뜻이다.

반드시 아래와 같이 Override 하여 사용하여야 한다. 안하면 컴파일러가 화가 나있을 것이다. 

public class ISBNDECS implements Comparator<Book>{
    
    // compare 함수 overriding으로 재정의
	@Override
	public int compare(Book o1, Book o2) {
		int reisbn = o1.getIsbn().compareTo(o2.getIsbn());
		if(reisbn != 0 ) {
			return -reisbn;
		} else {
			int req = Integer.compare(o1.getQuantity(), o1.getQuantity());
			return -req;
		}
	}
	

    // comparator를 통해 객체 정렬
	@Override
	public Book[] getISBNDESCList() {
		books.sort(new ISBNDECS());
		return books.toArray(new Book[books.size()]);
	}
}

compare 함수의 선언부를 보면 두 개의 파라미터를 받아야 하며 int형으로 return을 하는 것으로 보인다.

여기서 두 개의 파라미터는 비교할 두 객체를 의미하며 int는 1, 0, -1 값중 하나에 해당한다.

 

(반환형 분류)

 1 : o1 > o2 

 0 : o1 == o2

-1 : o1 < o2 

 

여기서 크다 작다는 단순히 특정 값이 크다, 작다 일수도 있지만 String형의 경우 사전적 순서를 의미하므로 (lexicographical하다라고 한다.) 사전상의 오름차순, 내림차순 정렬을 할 때 참고해야 한다.

(항상 String형은 예외가 있는 것 같다.)

 

더 깊숙하게 알면 좋겠지만 본인 경험상 실제로 사용할 때는 그냥 오름차순을 원할 때o1 < o2 인 경우로 return값이 -1이 되게 만들고, 내림차순일 때o1 > o2 인경우로 return 값을 1로 만들어서 쓴다.

 

그리고 0(두 객체가 같을 값일 경우) 다른 변수들을 비교하여 (파이썬의 lambda처럼) 그걸 기준으로 정렬을 해서 쓰면 된다.(모든 게 같은 값이면 애초에 정렬할 필요도 없으니 신경 안 써도 될 것 같다.)

 

심화버전으로 익명클래스 또는 lambda식으로 정렬을 할 수도 있으니 참고하시라.(아 파이썬 그립다...)

		
        // 익명 클래스 활용 (Collections 라이브러리 import해서 써야함)
       	Collections.sort(books, new Comparator<Book>() {
        	@Override
            public int compare(Book a, Book b){
            	// a, b가 서로 뺄 수 있는 값이 있을 경우 
                return a - b;
                // a, b 자체로는 연산 불가능하고 멤버변수 연산이 가능할 때
                return a.getPrice - b.getPrice;
            	}
        });
        
        
        // lambda 활용
        
        // a, b가 서로 뺄 수 있는 값이 있을 경우 
        books.sort((a, b)-> a - b); 
        
        // a, b 자체로는 연산 불가능하고 멤버변수 연산이 가능할 때
        books.sort((a, b)->{return a.getPrice() - b.getPrice();});

2. compareTo함수(Comparable 인터페이스)에 관하여

Comparable 인터페이스의 경우  f3을 누르거나 ctrl + 마우스 좌클릭을 할 경우 아래와 같은 내용을 확인할 수 있다.

Comparator의 compare 함수와 같이 이 친구도 compareTo를 재정의 해서 "써달라"고 애원하고 있다.

public class Book implements Comparable<Book>{

	public int compareTo(Book b2) {
		int reisbn = this.getIsbn().compareTo(b2.getIsbn());
		if(reisbn !=0) {
			return reisbn;
		}else {
			return Integer.compare(this.getQuantity(), b2.getQuantity());
		}
	}
    
}

comparator 람다나 익명클래스에서 비교할 수 없는 객체일 경우 비교할 수 있는 멤버변수 끼리 연산해서 return값 받으라고 표기해 뒀으나 해결책이 드디어 나왔다.

 

comparable 인터페이스를 구현하고, compareTo 함수를 클래스에 정의 하는 순간 그 객체는 비교가 가능한 객체로 돌변한다. 

 

compareTo 함수의 선언부를 보면 Comparator의 compare 함수의 선언부와 같이 int 리턴타입으로 지정되어 있다.

맞다. 똑같은 분류의 값(1,0,-1)을 반환한다. 다른 점은 파라미터 갯수 일 뿐이다. 아무래도 프로그래밍 언어는 영어 위주다보니 영어 자체로 보는게 직관적이다.

 

compare A to B = A와 B를 비교하다에서 A compareTo B => A는 주어 B는 비교할 대상(객체)로 A.compareTo(B)로 쓰면 된다.

 

위쪽의 소스코드와 같이 Book 클래스에는 Comparable 인터페이스 구현 및 compareTo 함수가 재정의 되어 있으므로 아래 소스코드처럼 정렬할 때 쓰면 된다.

 

또한 그냥 특정 객체와 comprareTo 내부 로직대로 비교하고 싶을 때는 그냥 a.compareTo(b)와 같이 쓰면 된다.

	public Book[] getISBNSortList() {   
		books.sort(new ISBNACS());
		return books.toArray(new Book[books.size()]);
	}
    
    
    
    // 그냥 특정 객체끼리 비교하고 싶을 때
    a.compareTo(b); // 1, 0, -1이 반환됨

이제 다 끝났다. 

아래의 소스코드를 보면 아름답지 아니한가?
		
        // 익명 클래스 활용 (Collections 라이브러리 import해서 써야함)
       	Collections.sort(books, (a, b) -> a - b);
        
        
        // lambda 활용
        // a, b가 자체적으로 비교 가능하도록 compareTo가 구현되어 있는 상태 
        books.sort((a, b)-> a - b);

 

 

 

728x90

댓글