Today I Learned
(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 역시 함수형 인터페이스 처럼 활용하도록 해야할 것이다.
'Today I Learned' 카테고리의 다른 글
(24.11.08) ELK 스택에 관하여 (1) (2) | 2024.11.08 |
---|---|
(24.11.05) DFS 응용과 백트래킹 (0) | 2024.11.05 |
(24.10.21) Message Queue 그리고 Redis (1) | 2024.10.21 |
(24.10.17) NginX 정리 + 본인 프로젝트에서 활용한 부분 추가 (1) | 2024.10.17 |
(24.10.14) Swagger & Spring REST Doc (2) | 2024.10.14 |