우선 전체 코드를 보면서 한 줄 한 줄 보자.
아래는 람다, 스트림을 활용하여 저장된 연산 결과들 중 Scanner로 입력받은 값보다 큰 결과값들을 출력하는 인터페이스를 작성한 코드이다.
package calculator;
import java.util.List;
import java.util.Scanner;
@FunctionalInterface
public interface ResultFilter {
void filter(List<Double> results);
ResultFilter resultFilter = (results) -> {
Scanner sc = new Scanner(System.in);
System.out.print("기준값을 입력하세요: ");
double filterNumber = sc.nextDouble();
List<Double> filtered = results.stream() // 스트림 활용
.filter(num -> num > filterNumber)
.toList();
if (filtered.isEmpty()) {
System.out.println("입력한 값보다 큰 결과가 없습니다.");
} else {
System.out.print("결과값들 : ");
filtered.forEach(num -> System.out.print(num + " / "));
System.out.println();
}
};
}
전체 구조를 간단하게 정리하면,
@FunctionalInterface
public interface ResultFilter {
void filter(List<Double> results);
ResultFilter resultFilter = (results) -> {
...
};
}
ResultFilter 는 filter 라는 추상 메서드 하나만 가지므로 '@FunctionalInterface'로 선언해주었다. 아래에서 자세하게 다뤄보자. (가장 핵심인 부분이기때문이다.)
void filter(List<Double> results);
이 부분이 가장 중요한데, 이 한 줄이 사실 이 인터페이스의 핵심 메서드 정의이다.
이건 ResultFilter라는 함수형 인터페이스의 추상 메서드 선언부이다.
- void
- 이 메서드는 반환값이 없다는 뜻이다.
- 즉, 뭔가 작업을 하지만 결과를 리턴하지 않고, 주로 출력하거나 내부에서 다른 처리를 한다.
- filter
- 이건 메서드 이름이다.
- 외부에서 이 인터페이스를 사용할 때는 filter()라는 이름으로 호출하게 된다.
- 이름 그대로, 주어진 리스트에서 특정 기준에 따라 필터링 작업을 수행할 것으로 예상할 수 있다.
- (List<Double> results)
- 이건 매개변수이다.
- results라는 이름의 Double형 리스트를 인자로 받는다.
정리하자면, resultFiler는 이 인터페이스를 구현하는 람다식으로 정의된 인스턴스이다.
자바의 @FunctionalInterface는 추상 메서드가 딱 하나만 있는 인터페이스이다.
그래서 이 filter 메서드는 ResultFilter 인터페이스가 람다식으로 사용할 수 있게 해주는 핵심 규약이다.
(results) -> {
// 무언가 실행
}
이 filter(List<Double> results) 메서드의 구현체 역할을 하는 것이다.
이제 내부의 람다식을 보자.
ResultFilter resultFilter = (results) -> {
Scanner sc = new Scanner(System.in);
System.out.print("기준값을 입력하세요: ");
double filterNumber = sc.nextDouble();
List<Double> filtered = results.stream() // 스트림 활용
.filter(num -> num > filterNumber)
.toList();
if (filtered.isEmpty()) {
System.out.println("입력한 값보다 큰 결과가 없습니다.");
} else {
System.out.print("결과값들 : ");
filtered.forEach(num -> System.out.print(num + " / "));
System.out.println();
}
};
사실 볼 부분은 아래 부분인데
List<Double> filtered = results.stream() // 스트림 활용
.filter(num -> num > filterNumber)
.toList();
- .filter(num -> num > filterNumber)
- .filter(...)는 스트림에서 조건에 맞는 요소만 남기는 중간 연산.
- num -> num > filterNumber는 람다 표현식: 리스트의 각 요소 num이 기준값보다 클 때만 필터링.
- 즉, 조건을 만족하는 값들만 스트림에 남긴다.
- .toList();
- 필터링된 결과를 리스트로 다시 수집
- 최종적으로 filtered는 기준값보다 큰 값들만 포함한 리스트가 된다.
자바 스트림에서 자주 사용하는 람다식을 표로 정리해두었으니 참고해보자.
| 메서드 | 람다식 예시 | 설명 |
| .filter() | list.stream().filter(x -> x > 10) | 조건을 만족하는 요소만 필터링 (여기선 10 초과) |
| .map() | list.stream().map(x -> x * 2) | 요소를 변환 (2배로 변환) |
| .forEach() | list.stream().forEach(x -> System.out.println(x)) | 각 요소에 대해 작업 수행 (출력 등) |
| .sorted() | list.stream().sorted((a, b) -> a - b) | 정렬 기준 지정 (오름차순 정렬) |
| .distinct() | list.stream().distinct() | 중복 제거 |
| .limit() | list.stream().limit(5) | 앞에서 5개 요소만 추출 |
| .skip() | list.stream().skip(3) | 앞에서 3개 요소 건너뜀 |
| .reduce() | list.stream().reduce(0, (a, b) -> a + b) | 누적 연산 (합계 계산 등) |
| .anyMatch() | list.stream().anyMatch(x -> x > 50) | 하나라도 조건 만족하면 true |
| .allMatch() | list.stream().allMatch(x -> x > 0) | 모두 조건 만족해야 true |
| .noneMatch() | list.stream().noneMatch(x -> x < 0) | 모두 조건 불만족해야 true |
| .collect() | list.stream().filter(...).collect(Collectors.toList()) | 최종적으로 결과 수집 (리스트 등으로) |
그리고 또 굉장히 중요한 정보!
또한, 인터페이스는 클래스와는 다르게 안에 static final 필드처럼 정의된 람다 인스턴스를 바로 가지고있어서, 별도의 클래스 인스턴스 생성없이 바로 접근할 수 있다. 만약, 클래스였다면 따로 객체를 호출해주어야하고 추가적으로 public static final ResultFilter resultFilter 이런 식으로 선언해주어야한다. 안그러면 메인 클래스에서 활용못해!
메인클래스에서 호출
ResultFilter.resultFilter.filter(resultHistory.getResults());
- ResultFilter: 인터페이스 이름
- .resultFilter: 정적으로 선언된 람다식 인스턴스
- .filter(...): filter 메서드를 호출해서 리스트를 전달
'Java > 계산기 구현' 카테고리의 다른 글
| 트러블 슈팅 - 기능 별로 클래스를 나누어보자 (0) | 2025.04.23 |
|---|---|
| 3단계 계산기 - try-catch 문을 사용한 예외처리 (0) | 2025.04.22 |
| 3단계 계산기 - enum 및 Generic을 활용한 데이터 분류 (0) | 2025.04.21 |
| 2단계 계산기 기능 정리 (0) | 2025.04.16 |
| 1단계 계산기 기능 정리 (0) | 2025.04.16 |