본문으로 바로가기
반응형

 

자바(Java)에서는 객체 또는 원시타입의 값을 정렬(sort)하기 위한 두 가지 대표적 인터페이스를 제공하고 있다. 그 중 우선 Comparable 인터페이스를 알아보도록 하자

 

1. Comparable

 

Comparable 인터페이스는 java.lang.Comparable 이라는 Package를 통해 제공되는 정렬을 위한 인터페이스이다.

이 인터페이스는 compareTo() 라는 단 하나의 method를 갖고 있다. 이를 통해 Instance 간의 정렬을 수행 시 Instance 내에 동일한 type의 값을 이용해 정렬을 수행할 수 있다.

자바(Java)에서 정렬을 수행할 수 있는 모든 Class들은 이 Comparable 인터페이스를 구현하고 있다. 정말로 그런지 아래의 Collection Class 내 실제 코드를 통해 확인해보자.

Integer Class의 상속 / 구현 구조
Double Class의 상속 / 구현 구조
String Class의 상속 / 구현 구조

 

위와 같이 정렬 가능한 Class가 Comparable 인터페이스를 구현하고 있는 것을 알 수 있다. 위와 같이 기본적인 정렬을 수행시킬 수 있는 형태의 Class를 Custom으로 생성하고 싶다면 Comparable Class를 구현하여 compareTo() 메소드를 구현하면 된다.

Comparable 인터페이스의 compareTo(T e) 메소드를 이용하면 객체 간 정렬을 수행 시, parameter로 전달되는 객체와 현재 객체 사이를 비교하여 정렬을 수행할 수 있게 된다.

compareTo()는 전달된 객체와 현재 객체의 특정 값을 상호 비교하여 양수 / 0 / 음수값 중 하나를 반환하게 된다. 다음의 예제를 통해 구현 방법에 대해 알아본다.

 

package com.test;

import java.util.*;

public class ComparableTest {
    public static void main(String[] args){
        Student s1 = new Student("Adam", 1);
        Student s2 = new Student("Aiden", 2);
        Student s3 = new Student("Michael", 3);

        List<Student> list = new ArrayList<>();
        list.add(s2);
        list.add(s1);
        list.add(s3);

        Iterator<Student> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println("#########################################");
        Collections.sort(list);
        Iterator<Student> iterator2 = list.iterator();
        while(iterator2.hasNext()){
            System.out.println(iterator2.next());
        }
    }
}

class Student implements Comparable<Student>{
    private String name;
    private int number;
    public Student(String name, int number){
        this.name = name;
        this.number = number;
    }
    public String getName(){
        return this.name;
    }
    public int getNumber(){
        return this.number;
    }

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

    @Override
    public int compareTo(Student student){
        return this.number - student.number;
        // 또는 아래와 같이도 코딩 가능
        // return Integer.compare(this.number, student.number);
        
        // Long 형 비교 시(Double, Float도 동일하게 사용 가능)
        // return Long.compare(this.number, student.number);
    }
}

/*
// 결과
Aiden : 2
Adam : 1
Michael : 3
#########################################
Adam : 1
Aiden : 2
Michael : 3
#########################################
-1
1
*/

 

위 코드를 통해서 compareTo(T e) method를 통해 현재 Class의 특정 값과 전달된 동일 Class의 특정 값을 비교하여 결과를 나타내고 있음을 알 수 있다.

현재의 값이 전달된 값보다 크면 양수를 반환하여 요소의 위치를 바꾸게 된다. 음수 또는 0이 나오면 더 작거나 같기에 정렬 순서가 유지된다.

즉, 현재의 값은 이미 저장되어 있고 전달된 값과 비교하였을 때, 전달된 값이 더 크면 현재의 정렬을 바꾸지 않는다는 의미다.

그래서 상단의 코드와 같이 구현하면 기본적으로 비교 기준의 값을 대상으로 오름차순 정렬이 되며, 반대로 전달된 객체의 값을 좌측에 놓고 구현하면 내림차순으로 구현이 된다.

즉, Comparable는 Natural Ordering을 구현하기 위한 인터페이스라고 볼 수 있다. 아래의 코드를 통해 정말 그런지 보자.

 

package com.test;

import java.util.*;

public class ComparableTest {
    public static void main(String[] args){
        Student s1 = new Student("Adam", 1);
        Student s2 = new Student("Aiden", 2);
        Student s3 = new Student("Michael", 3);

        List<Student> list = new ArrayList<>();
        list.add(s2);
        list.add(s1);
        list.add(s3);

        Iterator<Student> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println("#########################################");

        Collections.sort(list);
        Iterator<Student> iterator2 = list.iterator();
        while(iterator2.hasNext()){
            System.out.println(iterator2.next());
        }

        System.out.println("#########################################");

        int retVal = s1.compareTo(s2);
        int retVal2 = s3.compareTo(s2);

        System.out.println(retVal);
        System.out.println(retVal2);
    }
}

class Student implements Comparable<Student>{
    private String name;
    private int number;
    public Student(String name, int number){
        this.name = name;
        this.number = number;
    }
    public String getName(){
        return this.name;
    }
    public int getNumber(){
        return this.number;
    }

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

    @Override
    public int compareTo(Student student){
        return student.number - this.number;
    }
}

/*
Aiden : 2
Adam : 1
Michael : 3
#########################################
Michael : 3
Aiden : 2
Adam : 1
#########################################
1
-1
*/

 

이 코드를 통해 실제로 정렬 순서가 바뀌었음을 확인할 수 있게 되었다.

 

 

2. Comparator

 

Comparator 인터페이스는 Comparable 인터페이스와 같이 정렬의 기준을 정해줄 수 있는 인터페이스임은 동일하지만 java.util package에서 위치하며 compare(Object o1, Object o2) method를 구현해야 한다는 차이점이 있다.

또한 Comparable은 Natural Ordering을 구현하기 위해 사용하였다면, Comparator는 별도의 특정 기준을 통해 구현하기 위해 사용되는 인터페이스 이다.

 

예를 들어 위 Student Class의 정렬 방식을 학생의 이름의 길이로 바꾸고 싶다던지 등의 별도의 기준을 마련하고 싶다면 어떨까?

평시에는 일반적인 Ordering으로 정렬하다가 특정 Class의 어떤 목적으로 사용 시에만 그 정렬 기준을 바꾸고 싶다고 생각해보자.

그렇다면 그 특정 목적의 사용 시를 위해 기본적인 정렬 방식을 바꾸는 게 옳은 방법일까? 그렇지 않다.

그러므로 익명의 Class인 Comparator Class를 구현해서 해당 정렬 기준을 별도로 만들고 sort method를 사용 시 지정한 Comparator 객체를 전달하여 그 경우에만 전달 기준을 바꾸어 주도록 사용할 수 있게 되는 것이다. 아래의 코드 예시를 보자.

 

package com.test;

import java.util.*;

public class Test {
    public static void main(String[] args){
        Student s1 = new Student("Adam", 1);
        Student s2 = new Student("Aiden", 2);
        Student s3 = new Student("Michael", 3);
        Student s4 = new Student("SomeLongName", 4);
        Student s5 = new Student("VeryVeryLongName", 5);

        List<Student> list = new ArrayList<>();
        list.add(s2);
        list.add(s5);
        list.add(s4);
        list.add(s1);
        list.add(s3);
        
        // Comparator 부분
        Comparator<Student> comparator = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o2.getName().length() - o1.getName().length();
            }
        };
        Collections.sort(list, comparator);
        Iterator<Student> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
}

class Student implements Comparable<Student>{
    private String name;
    private int number;
    public Student(String name, int number){
        this.name = name;
        this.number = number;
    }
    public String getName(){
        return this.name;
    }
    public int getNumber(){
        return this.number;
    }

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

    @Override
    public int compareTo(Student student){
        return student.number - this.number;
    }
}

/*
// 결과
VeryVeryLongName : 5
SomeLongName : 4
Michael : 3
Aiden : 2
Adam : 1
*/

 

위의 코드를 통해 Student 객체의 이름의 length 를 기반으로 내림차순 정렬을 진행한 것을 확인할 수 있다. 2번째로 전달된 객체가 더 큰 경우 양수를 반환하여 정렬 순서를 바꾸었기에 내림차순으로 정렬되었으며 오름차순으로 정렬하고 싶은 경우 반대로 뺄셈을 수행하면 된다.

Collections Class는 Comparable을 구현하고 있고 인자로 Comparator 객체를 받아 Collection Class의 자료 구조를 정렬할 수 있다. 오늘 포스팅 한 두 가지 인터페이스를 통해 어떤 방식으로든 정렬할 수 있으며 이를 잘 활용하면 알고리즘 구현 시에도 훨씬 수월할 것이다.

참고로 Arrays Class는 배열을 정렬할 수 있는데 Collections Class 처럼 Comparator를 인자로 받을 수 있고, Comparable을 구현하였기에 Natural Ordering으로 설정하는 것 또한 가능하다.

실제 자바(Java)에서는 이러한 정렬을 수행 시의 성능을 고려하여 Merge 정렬, Quick 정렬 등의 알고리즘을 사용하여 정렬을 수행하고 있다. 이러한 정렬 방식에 대해서는 추후에 별도로 포스팅을 진행하도록 하겠다.(Arrays의 sort는 Tim Sort(Merge + Insertion), Collections의 sort는 Dual Pivot Quick Sort(Quick + Insertion) 방식을 쓴다.)

 

Comparable과 Comparator의 차이

Comparable Comparator
Natural Ordering에 사용되는 인터페이스 서로 다른 Object 간의 특성을 기준으로 별도의 정렬 기준을 지정하기 위한 인터페이스
java.lang package를 통해 구현 java.util package를 통해 구현
기존의 Original Class에 영향을 준다. Original Class에 영향을 주지 않는다.

 

참고

익명클래스란? 

다른 Instance를 이용해 객체를 생성하는 Class와는 다르게 이름이 없는 Class를 의미한다. 선언과 객체의 생성을 동시에 진행하여 단 한 번만 사용될 수 있고 하나의 객체만 생성하는 것이 가능하다. 특정 Class 내에서 선언 / 객체 생성이 동시에 진행 되고 해당 Class 내에서만 사용 가능하기 때문에 외부에서 참조도 불가하며 별도의 파일로 생성할 필요가 없다는 장점이 있다.(외부에 인자로 전달하는 것은 가능하다)

상단에서 Comparator를 구현할 때, Class 선언과 동시에 객체를 생성하며 필요한 method를 오버라이딩하여 구현하였다. 이와 같은 방식으로 구현하는 것이 익명 클래스이다. 이와 같이 일회성으로 즉각 사용될 필요는 있지만 별도의 Class로 구현할 필요가 없다면 익명클래스가 유효하다.

 

 

오류가 있는 부분은 댓글로 남겨주시면 반영하겠습니다. 감사합니다.

반응형