ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Comparator와 Comparable
    언어/Java 2021. 6. 13. 19:20

    Comparator와 Comparable은 둘 다 객체를 정렬할 때 사용할 수 있는 기능입니다. 먼저 아래와 같이 객체가 존재한다고 가정합니다.

    public class Image {
        private final int type;
        private final String url;
        private final int idx;
    
        public Image(int type, String url, int idx) {
            this.type = type;
            this.url = url;
            this.idx = idx;
        }
    
        public int getIdx() {
            return idx;
        }
    }

    다음 아래와 같이 리스트에 위 객체들을 입력합니다.

    List<Image> images = new ArrayList<>();
    
    images.add(new Image(3, "/img/1", 0));
    images.add(new Image(3, "/img/2", 4));
    images.add(new Image(3, "/img/3", 2));
    images.add(new Image(4, "/img/4", 3));

    여기서 이제 객체의 idx 오름차순으로 정렬을 하려고 한다면 사용할 수 있는 기능은 Comparator와 Comparable가 될 수 있습니다.

    Comparable 인터페이스

    Comparable을 사용하여 정렬을 진행한다면 리스트의 값인 Image객체에 해당 인터페이스를 상속받아 구현을 하는 방식입니다.

    Comparable 인터페이스는 compareTo 메소드 1개만 포함하고 있어 정렬 순서를 해당 메소드에 구현하면 됩니다. 여기서 compareTo 메서드에 값이 넘어오게 되는데 정수형으로 반환해야 합니다. 해당 예시에서는 오름차순으로 정렬하고자 하므로 (메서드를 호출하는 객체 - 인자로 넘어온 객체)로 표현하면 됩니다. 예를 들어, 인자로 넘어온 이미지의 idx는 4이고 메서드를 호출한 이미지의 idx가 2라면 (2 - 4)의 값을 반환하게 되고 이는 메서드를 호출한 이미지가 인자로 넘어온 이미지보다 작다는 것을 의미합니다.

    public class Image implements Comparable<Image> {
        private final int type;
        private final String url;
        private final int idx;
    
        public Image(int type, String url, int idx) {
            this.type = type;
            this.url = url;
            this.idx = idx;
        }
    
        public int getIdx() {
            return idx;
        }
    
        @Override
        public int compareTo(Image o) {
            return getIdx() - o.getIdx();
        }
    }

    이는 아래와 같이 Collections 를 통해 정렬할 수 있습니다.

    List<Image> images = new ArrayList<>();
    
    images.add(new Image(3, "/img/1", 0));
    images.add(new Image(3, "/img/2", 4));
    images.add(new Image(3, "/img/3", 2));
    images.add(new Image(4, "/img/4", 3));
    
    Collections.sort(images);
    
    for (Image img: images) {
        System.out.println(img.getIdx());
    }
    //0
    //2
    //3
    //4

    이 방법은 정렬 대상이 되는 클래스를 직접 수정하는 방식이므로 초기 설계에서 고려하여 구현할 때 좋은 방식입니다.

    Comparator 인터페이스

    만약 초기 고려를 하지 않았거나 기존 코드를 수정할 수 없는 상황이거나 선언된 정렬 기준 이외의 다른 기준으로 정렬을 하고 싶다면 Comparator 인터페이스를 사용해 당시의 기준에 따라 정렬을 할 수 있습니다.

    이번에는 Comparator 인터페이스를 사용해 내림차순으로 정렬한다고 가정합니다.

    Comparator<Image> comparator = new Comparator<Image>() {
        @Override
        public int compare(Image a, Image b) {
            return b.getIdx() - a.getIdx();
        }
    };
    
    // Collections.sort(images, comparator);
    images.sort(comparator);
    
    for (Image img: images) {
        System.out.println(img.getIdx());
    }
    //4
    //3
    //2
    //0

    위에서는 Collections를 사용할 수도 있지만 images의 타입인 List가 Collection을 확장하고 있어 대신 사용해도 동일한 결과를 보여줍니다.

    이 예시는 익명함수를 사용해 나타냈지만 자바 8이후에 람다가 나왔으므로 아래와 같이 좀 더 직관적으로 람다식 표현으로 진행할 수 있습니다.

    // 방법 1
    // Comparator<Image> comparator = (a, b) -> b.getIdx() - a.getIdx();
    // images.sort(comparator);
    
    // 방법 2
    // Collections.sort(images, (a, b) -> b.getIdx() - a.getIdx());
    
    images.sort((a, b) -> b.getIdx() - a.getIdx());
    
    for (Image img: images) {
        System.out.println(img.getIdx());
    }
    //4
    //3
    //2
    //0

    위와 같이 총 3가지 방법이 있고 모두 동일한 결과를 보여줍니다.

    추가적으로 Stream 클래스의 sorted() 메서드도 Comparator 를 받아 처리를 하므로 아래와 같이 표현할 수도 있습니다. 여기서 차이점은 Comparable이나 Comparator는 기존 리스트에 직접 순서를 변경하였다면 Stream의 sorted 메서드는 기존 리스트의 순서는 유지하고 주어진 조건에 따라 정렬한 새로운 객체를 반환합니다.

    List<Image> result = images.stream()
            .sorted((a, b) -> b.getIdx() - a.getIdx())
            .collect(Collectors.toList());
    
    for (Image img: result) {
        System.out.println(img.getIdx());
    }

    요약

    정렬 대상의 객체에 대해 초기에 또는 기본 정렬 값을 주려면 Comparable 인터페이스 사용

    정렬 대상의 객체를 수정하지 못하거나 그때마다 다른 정렬 기준을 주려면 Comparator 인터페이스 사용

    기존 리스트는 그대로 유지하고 주어진 정렬 조건에 맞는 새로운 리스트 객체를 얻으려면 Stream의 sorted() 메서드 사용

    댓글