본문 바로가기
Today I Learned 2024. 10. 29.

(24.10.29) Comparator 인터페이스와 람다식

Java에서 @FunctionalInterface Annotation이 붙은 함수형 인터페이스는

@FunctionalInterface
interface 인터페이스이름 { 인터페이스 정의 }
// 람다식을 사용할 수 있는 함수형 인터페이스 정의를 할때 Annotation 사용

 

위와 같은 형태로 띄어지게 되고, 익명 클래스처럼 { } 안에 해당 함수형 함수에 해당하는 

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from the anonymous class!");
            }
        };

        // 스레드를 생성하고 익명 클래스의 run 메서드 실행
        Thread thread = new Thread(runnable);
        thread.start();
        
         ...

부분을 만들 수 있고, 그리고 이는 

...
        // 람다식을 사용하여 Runnable 인터페이스 구현
        Runnable runnable = () -> System.out.println("Hello from the lambda expression!");

        // 스레드를 생성하고 람다식의 run 메서드 실행
        Thread thread = new Thread(runnable);
        thread.start();
...

이렇게 람다식으로도 변환을 해서 사용할 수 있게끔 할 수 있다.

 

그런데 이러한 방법은 @FunctionalInterface Annotation이 붙은 인터페이스, 즉 함수형 인터페이스를 따로 만들어서 사용만 할 줄 알았지만,

Comparator 역시 @FunctionalInterface Annotation이 붙으면서 Java 8 이후부터는 자유롭게 위의 함수형 인터페이스의 익명클래스, 더 나아가 간단한 람다식으로 sort() 메서드에서 정렬 조건을 부여할 수 있다는 것을 알게 되었다.


스스로 만든 예제

import java.util.*;

class Solution {
    public int[] solution(String[] categories, int[] counts) {
        Map<String, Integer> categoryTotalCounts = new HashMap<>();
        Map<String, List<int[]>> categoryIndexCounts = new HashMap<>();
        
        for (int i = 0; i < categories.length; i++) {
            String category = categories[i];
            categoryTotalCounts.put(category, categoryTotalCounts.getOrDefault(category, 0) + counts[i]);
            
            categoryIndexCounts.putIfAbsent(category, new ArrayList<>());
            categoryIndexCounts.get(category).add(new int[]{i, counts[i]});   
        }
        
        List<String> categoryList = new ArrayList<>(categoryTotalCounts.keySet());
        categoryList.sort((c1, c2) -> categoryTotalCounts.get(c2) - categoryTotalCounts.get(c1));**
        
        List<Integer> answerList = new ArrayList<>();
        for (String category : categoryList) {
            List<int[]> itemInfoList = categoryIndexCounts.get(category);
        
            itemInfoList.sort((i1, i2) -> {
                if (i1[1] == i2[1]) {
                    return i1[0] - i2[0];
                } else {
                    return i2[1] - i1[1];
                }
            });
            
            for (int i = 0; i < Math.min(itemInfoList.size(), 2); i++) {
                answerList.add(itemInfoList.get(i)[0]);
            }
        }
    
        return answerList.stream().mapToInt(i -> i.intValue()).toArray();
    }
}

Comparator의 사용

// 학생을 정렬하기 위한 Comparator 클래스
class StudentComparator implements **Comparator<Student>** {
    **@Override**
    public int **compare**(Student s1, Student s2) {
        if (s1.score == s2.score) {
            return s1.name.**compareTo**(s2.name); // 이름 오름차순
        }
        return Integer.**compare**(s2.score, s1.score); // 성적 내림차순
    }
}

...

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 85),
            new Student("Bob", 90),
            new Student("Charlie", 85)
        );

        Collections.sort(students, **new StudentComparator()**);
        for (Student student : students) {
            System.out.println(student.name + ": " + student.score);
        }
    }
}
  • Comparator 인터페이스를 사용하기 위해서는 compare 메서드를 다시 설정하는 방식
    • 이를 위해서 별도의 Comparator 클래스를 정의가 필요할 수 있음
    • Comparator 인터페이스의 어떤 구현체를 만드는 과정
  • A.compareTo(B)
    • Comparable 인터페이스 메서드 이지만, 바로 사용해서 순서를 비교할 수 있음
  • compare(A, B)
    • Comparator의 기본적인 compare 메서드
    • 모든 래퍼 클래스는 compare을 통해서 비교할 수 있음
  • compare 의 반환 타입은 int 즉, -1 0 1 셋중에 하나여야함
    • 음수: 현재 객체가 매개변수 객체보다 작음
    • 0: 두 객체가 같음
    • 양수: 현재 객체가 매개변수 객체보다 큼
public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 85),
            new Student("Bob", 90),
            new Student("Charlie", 85)
        );

        // 익명 클래스를 사용하여 Comparator 정의
        Collections.sort(students, **new Comparator<Student>()** {
            @Override
            public int compare(Student s1, Student s2) {
                if (s1.score == s2.score) {
                    return s1.name.compareTo(s2.name); // 이름 오름차순
                }
                return Integer.compare(s2.score, s1.score); // 성적 내림차순
            }
        });

        for (Student student : students) {
            System.out.println(student.name + ": " + student.score);
        }
    }
}
  • 또는 새로운 new Comparator<>() 를 통해서 {} 안에서 compare 메서드를 재정의해야할 수 있음
    • 단, 이 부분도 코드의 단순화가 필요하지 않을 수 있음

Comparator → 람다식으로 구현

  • Java 8 으로 들어오면서 전통적으로 실행이 되었 여러 인터페이스가 함수형 인터페이스로 선언할 수 있도록 지원하기 시작했음
    • Comparator 역시 함수형 인터페이스의 일종이기 때문에 람다식으로 대체가 가능
  • @FunctionalInterface Annotation이 붙은 인터페이스들은 모두 함수형으로 대체하는 방향으로 바뀔 수 있음
    • 원래 함수형 인터페이스로 지정된 Custom 인터페이스 외에도 기존의 Comparator 같은 인터페이스도 @FunctionalInterface를 가지고 있기 때문에 가능 하다는 것
...

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 85),
            new Student("Bob", 90),
            new Student("Charlie", 85)
        );

        // 람다식을 사용하여 정렬
        students.sort(**(s1, s2) -> {
            if (s1.score == s2.score) {
                return s1.name.compareTo(s2.name); // 이름 오름차순
            }
            return Integer.compare(s2.score, s1.score); // 성적 내림차순
        })**;

        for (Student student : students) {
            System.out.println(student.name + ": " + student.score);
        }
    }
}

  • (A, B)를 기준으로 sort() 메서드 매개변수로 람다식을 넣을 수 있음
  • { } 괄호 안에 바로 적용할 수 있도록 가능
 **itemInfoList.sort((i1, i2) -> {
                if (i1[1] == i2[1]) {
                    return i1[0] - i2[0]; // 오름차순
                } else {
                    return i2[1] - i1[1]; // 내림차순
                }
            });**
  • (A, B) → { } 에서 람다식의 {} 람다 본체의 반환은 Comparator처럼 -1, 0, 1 셋중 하나여야
    • -1 일경우 : A가 B 보다 작기 때문에, 오름차순으로 정렬
    • 0일 경우 : 같다고 판단 위에서는 그대로 유지
    • 1일 경우 : A가 B 보다 크기 때문에, 내름차순으로 정렬
  • 따라서 앞에서 위를 빼는 형식은 오름차순, 뒤에서 앞을 빼는 방식은 내림차순임

기존의 코드 또는 로직에서 위와 같은 Comparator 를 활용하기 위해서는 간단한 로직으로라도

Comparator 인터페이스를 구현하는 클래스를 하나만들어서 내지는 익명 클래스를 만들어서 활용하는 방법으로 진행을 했지만,

 

위와 같이 간단하게 람다식으로만으로도 함수형 인터페이스를 활용해선 빠르게 sort를 진행시킬 수 있다는 점으로,

Comparator 역시 함수형 인터페이스 처럼 활용하도록 해야할 것이다.