티스토리 뷰

자바 로고

 

 

 

JAVA API의 공식 문서를 보거나, STS, IntelliJ 같은 툴을 사용하면

Collections.sort(List<T> list , Comparator<T> c) 같이 Comparator를 볼 수가 있습니다.

 

 

저는 이것을 어떻게 작성해야하는 건지, 어떻게 사용하는 건지 자세히 몰랐었습니다.

그래서 다른 개발자 분들에게도 이 글만 보고 어떻게 사용하는지 감을 잡을 수 있게 작성하게 되었습니다.

 

 

 

Comparable과 Comparator

 

Comparable과 Comparator를 사용하는 이유는 객체를 비교하기 위해 사용한다.

객체를 비교한다는건 뭘까? 글자비교? 순서비교?

비교를 하여 오름차순인지 내림차순인지 어떻게 정렬하기 위하여 사용하는 것이다. 

 

 

Comparable&#44; Comparator

 

 

좀 이론적으로 설명하자면, 

Comparable은 클래스 자체에서 기본적인 정렬 기준을 제공하는 인터페이스입니다.
즉, Comparable 인터페이스를 구현하면 그 클래스의 객체들은 기본적으로 자연적인 순서에 따라 정렬됩니다.

 

Comparator은 외부에서 추가적인 정렬 기준을 정의하여 객체들을 다양하게 정렬하는데 사용하는 인터페이스입니다.
즉, Comparator 인터페이스를 구현하면 우리가 객체들을 다양한 방법으로 정렬할 수 있다.

앞으로 우리가 오버라이딩을 하여 우리가 원하는 대로 정렬할 수 있게 Comparator를 배울 것입니다!

 

 

 

 

 

 

Comparator을 활용하기

 

인터페이스 구현 예시

 

비교하고 싶은 객체에 어떤 값(필드 값)을 비교하기 위해서 비교하고 싶은 객체에 implements 구현을 하여 메서드를 오버라이딩 해주었다.

예를 들면 학생 클래스를 만들었고 필드 값으로 나이, 반, 이름, 성별 등이 있다고 하자.

그리고 대학생을 생각하여 나이 별로 Comparator을 구현한 compare메서드를 오버라이딩 해준 예제이다.

 

import java.util.Comparator;

class Student implements Comparator<Student> {

    int age;	        	// 나이
    int classNumber;    	// 반
    String name;              // 이름
    char gender;              // 성별

    public Student(){};

    public Student(int age, int classNumber, String name, char gender) {
        this.age = age;
        this.classNumber = classNumber;
        this.name = name;
        this.gender = gender;
    }

    @Override
    public int compare(Student s1, Student s2) {

        /*
         * 만약 s1의 나이가 s2의 나이보다 많으면 양수가 반환 될 것이고,
         * 같다면 0을,
         * s1의 나이가 s2의 나이보다 작다면 음수를 반환할 것이다.
         */
        return s1.age - s2.age;
    }
}

 

위에 예시 코드처럼 return 값을 비교하고 싶은 것끼리 빼줘도 된다.

Student 객체를 만들어 줬으니 인스턴스 2개를 나이를 다르게 만들어서 Compare 메서드를 사용하여 실험해보시면 음수, 양수의 값이 나오게 됩니다.

 

 

 

그런데!? 드물지만 두 객체의 어떠한 필드 값을 빼줄 때 자료형의 범위를 벗어나는 단점이 생깁니다.

 

 

 

 

주의 할 점

두 수의 대소비교를 할 때 해당 자료형의 끝에서 연산을 하게 되면 오류가 발생하는 것이다.

예를 들면 int형 자료형 제일 낮은 범위인 -2,147,483,648에서 -1을 하게 된다면 2,147,483,647으로 int형의 최대 값으로 반환되는 문제가 발생한다. 해당 문제를 "Underflow" 라고 합니다.

또한, 제일 높은 범위인 2,147,483,647에서 +1을 하게 된다면 -2,147,483,648으로 int형의 최솟값으로 반환되는 문제가 발생합니다. 해당 문제를 "Overflow" 라고 합니다.

public class Test {
	public static void main(String[] args) {
 
		int min = Integer.MIN_VALUE;	  	// MIN_VALUE는 -2,147,483,648 이다.
		int max = Integer.MAX_VALUE;	 	// MAX_VALUE는 2,147,483,647 이다.
 
		System.out.println("min - 1 = " + (min - 1));
		System.out.println("max + 1 = " + (max + 1));
	}
}

// 출력
// min - 1 = 2147483647
// max + 1 = -2147483648

 

 

 

 

 

익명클래스 사용하기

 

이번에는 학생 클래스에서 Comparator 구현을 안 한 상태이다.

class Student{

    int age;		 	// 나이
    int classNumber;	 	   // 반
    String name;        	 // 이름
    char gender;        	 // 성별

    public Student(){};
    public Student(int age, int classNumber, String name, char gender) {
        this.age = age;
        this.classNumber = classNumber;
        this.name = name;
        this.gender = gender;
    }
}

 

이제 아래 코드를 보면 익명클래스를 사용하여 Comparator를 만들어 보았습니다.

Comparator자체로 인터페이스이기 때문에 { } 안에 @Override를 작성하여 구현을 해주었고 이것을 Comparator 인터페이스 클래스 참조변수 comp에 담아 주었습니다.

 

public class Test {
    public static void main(String[] args)  {

        Student seok = new Student(19, 2, "유재석", 'M');	// 19살 2반 유재석 남자
        Student chae = new Student(18, 1, "이채령", 'F');	// 18살 1반 이채령 여자
        Student jung = new Student(15, 3, "정동혁", 'M');	// 15살 3반 정동혁 남자


        // 나이 기준 익명객체를 통해 객체를 비교한다.
        int ageGap = comp.compare(seok, chae);

        if(ageGap > 0) {
            System.out.println("재석이가 채령이보다 나이가 많습니다.");
        }
        else if(ageGap == 0) {
            System.out.println("두 객체의 나이 크기가 같습니다.");
        }
        else {
            System.out.println("재석이가 채령이보다 나이가 적습니다.");
        }

    }

    // 나이 대소 비교 익명 객체
    public static Comparator<Student> comp = new Comparator<Student>() {
        @Override
        public int compare(Student s1, Student s2) {
            if(s1 instanceof Student && s2 instanceof Student){
                return s1.age - s2.age;
            }
            return -1;
        }
    };
}

/*
  출력 : 재석이가 채령이보다 나이가 많습니다.
 */

 

이렇게 보면 익명 클래스를 만들어서 비교하고 싶은 객체로 Generic 설정을 해주었습니다.

그 다음 객체에서 비교하고 싶은 나이필드를 각 객체에서 빼주었습니다. (s1.age - s2.age)

이렇게 나이뿐만 아니라 반, 이름으로도 compare(비교) 할 수 있으며 이 값으로 객체들을 비교하여 정렬시킬 수 도 있겠습니다.

 

 

 

 

 

Comparator를 이용하여 Array,  Collections정렬하기

 

Student 클래스가 아래와 같이 있자고 하자

console창에 확인하기 위해 toString()을 오버라이딩 해주었습니다. 안해주면 객체가 존재하는 메모리 주소만 나오게 됩니다.

class Student{

    int age;	       		// 나이
    int classNumber;	   // 반
    String name;           // 이름
    char gender;           // 성별

    public Student(){};
    Student(int age, int classNumber, String name, char gender) {
        this.age = age;
        this.classNumber = classNumber;
        this.name = name;
        this.gender = gender;
    }

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

 

 

아래의 코드를 본다면 List에 Student객체를 담았고 Comparator를 구현하여 나이순 정렬을 생성해주었다.

public class Test {
    public static void main(String[] args)  {

        Student seok = new Student(19, 2, "유재석", 'M');	// 17살 2반 유재석 남자
        Student chae = new Student(18, 1, "이채령", 'F');	// 18살 1반 이채령 여자
        Student jung = new Student(15, 3, "정동혁", 'M');	// 15살 3반 정동혁 남자

        List<Student> list = new ArrayList<>();
        list.add(chae);     // 이채령 추가
        list.add(jung);      // 정동혁 추가
        list.add(seok);     // 유재석 추가

        list.forEach(System.out::println);

        Collections.sort(list, comp);	// 이 때 정렬

        list.forEach(System.out::println);

    }

    // 나이 대소 비교 익명 객체
    public static Comparator<Student> comp = new Comparator<Student>() {
        @Override
        public int compare(Student s1, Student s2) {
            if (s1 instanceof Student && s2 instanceof Student) {
                return s1.age - s2.age;     // 양수면 그대로, 음수면 위치 변경 즉, 오름차순
            }
            return 1;
        }
    };
}

 

Comparator 메서드에서 반환 값이 음수 일 때 두 객체의 순서는 바뀌고 양수 일 때는 그대로 정하게 되는 것입니다.

그대로 정렬하게 된다는 것은 기본적으로 오름차순으로 된다는 것입니다.

 

// 원래 순서
이채령: 18
정동혁: 15
유재석: 19

// 정렬 후 출력 (나이 오름 차순)
정동혁: 15
이채령: 18
유재석: 19

 

 

 

 

 

Comparator 을 람다식으로 변환하기

 

1.  원래 코드

방금 했던 예제로 다시 코드를 작성하였다.

저희는 Comparator를 하나 생성하여 Override를 하여 그 걸 Comparator 참조 변수에 넣고 정렬할 때 변수를 넣어서 사용하였습니다.

Comparator에서 람다식으로 변경

 

 

2. 람다식으로 변경

가독성이 편하게 람다식으로 변경하였고 compare같은 메서드 이름은 필요 없게 되었습니다.

왜냐하면 익명 함수이기 때문에 실질적으로 필요 없어 진 것입니다.

Comparator에서 람다식으로 변경

 

 

3. 메서드를 생성하지 않고 안에다가 넣기

참조 변수에 담지 않고 람다식을 바로 Collections.sort 안에다가 넣어 주었습니다.

정렬할 조건들이 많고 코드도 길어지면 오히려 가독성이 떨어지겠지만 코드도 몇 줄 안되어서 넣어 주었습니다.

Comparator에서 람다식으로 변경

제가 이 글을 작성한 이유가 Collections.sort에서 Comparator를 내가 오버라이딩으로 작성하여 원하는 대로 정렬하고 어떤 식으로 정렬되는지 알려주고 어떻게 사용하는지 알려주기 위함이였습니다. 

이제야 조금은 감은 잡히나요!?  그럼 이제 코딩테스트에 사용한 코드를 알려드리겠습니다.

 

 

해피고양이

 

 

 

 

사용한 실제 코드

 

저는 알파벳 소문자로 이루어진 N개의 단어가 들어오면 아래와 같은 조건에 따른 정렬하는 것을 구현하고자 하였습니다.

  1. 길이가 짧은 것부터
  2. 길이가 같으면 사전순으로
  3. 중복된 단어는 하나만 남기고 제거

백준 문제

 

 

 

 

Collection 안에 람다식으로 Comparator를 구현

 

백준 문제 구현

 

내가 정렬 조건을 구현한 코드

이렇게 개발자가 원하는 조건대로 원하는 대로 정렬 할 수 있도록 Comparator를 구현하는 방법을 알아보았습니다.

 

 

 

 

코드 보기

import java.io.*;
import java.util.*;


public class Test {
    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int size = Integer.parseInt(br.readLine());
        Set<String> set = new HashSet<String>();	// 중복제거하기 위해 set 사용

        for(int i = 0; i < size; i++) {
            set.add(br.readLine());
        }

        // set을 list로 변경
        ArrayList<String> list = new ArrayList<String>(set);

        // 컬렉션 정렬 오버라이딩하기
        Collections.sort(list, (e1, e2) -> {
            // 문자열 길이가 같으면
            if(e1.toString().length() == e2.toString().length()) {
                // 문자 차이를 보고 정렬
                for(int i = 0 ; i <e1.toString().length(); i++) {
                    if (e1.toString().charAt(i) == e2.toString().charAt(i)) {
                        continue;	// 같은 문자면 다음 문자로 검사
                    } else {
                        return e1.toString().charAt(i) - e2.toString().charAt(i);
                    }
                }
            } else {
                //문자열길이 순서대로 정렬
                return e1.toString().length() - e2.toString().length();
            }

            return 1;
        });


        for(int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

}

 

 

출처

해당 문제는 백준에서 진행되었습니다.

백준 사이트 : https://www.acmicpc.net/problem/1181

 

1181번: 단어 정렬

첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000) 둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주어지는 문자열의 길이는 50을 넘지 않는다.

www.acmicpc.net

 

 

 

감사합니다.

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함