public Main {
	public static void main(String[] args) {
    	// list 생성
        List<String> list = new ArrayList<>();
        list.add("기러기");
        list.add("토마토");
        list.add("팡팡팡");
        
        // list 외부 반복자 출력
        Iterator iterator = list.iterator();
        while(iterator.hasNext()) {
        	System.out.println(iterator.next());
        }
        
        // list 내부 반복자 출력(stream)
        Stream<String> stream = list.stream();
        stream.forEach(x -> System.out.println(x));
    }
}

stream.forEach(x -> System.out.println(x));를 좀 더 알아보기 위해서 JDK API Document를 참고해서 살펴보았다.

 

Stream 인터페이스를 보면 forEach 메소드가 정의되어 있는데 forEach는 Consumer 인터페이스를 구현한 객체를 하나 받는다. Consumer 인터페이스를 타고 들어가면 

 

@FunctionalInterface
public interface Consumer<T> {
	void accept(T t);
}

Consumer 인터페이스는 추상 메서드가 하나뿐인 함수형 인터페이스이며, 이로 인해 accept 메서드를 람다 표현식으로 구현할 수 있다. @FunctionalInterface 애노테이션은 이러한 구조를 컴파일 타임에 검증하고, 해당 인터페이스가 람다 사용을 의도했음을 명확히 하기 위한 장치다.

 

 

default 메소드도 포함되어 있는데, 추상 메소드만 1개만 있으면 되므로 default 메소드는 상관 없다. ⭐위 내용을 종합해보면, Consumer<T> 클래스는 T를 하나 받아서 void 처리하는 인터페이스이다.

 

stream.forEach(x -> System.out.println(x));

돌아가서 자바가 위와 같은 코드를 볼 때,

 

forEach(Consumer<? super T> action)

컴파일러는 위와 같이 생각한다. 따라서 Consumer가 필요하고 이 람다는 Cosumer.accept( )를 구현했다고 생각한다.

 

stream.forEach(x -> System.out.println(x));
stream.forEach(new Consumer<T>() {
    @Override
    public void accept(T x) {
        System.out.println(x);
    }
});

위 아래 두코드는 완전히 동일하다. 좀 더 풀어서 설명하자면 forEach의 매개변수는 Consumer<T>가 들어올 수 있는데, Consumer<T>는 인터페이스이다. 여기에 @FunctionalInterface 어노테이션이 달려 있으므로 당연히 추상메소드가 한 개 있고, 이를 우리가 람다식 또는 메서드 참조로 구현해서 전달해야 한다. 즉 @FunctionalInterface의 말 그래도 함수형 프로그래밍, 즉 추상 메서드의 구현(동작)을 정의하고, 그 구현을 데이터 처리 메서드(위를 예를 들면 forEach)에 전달한다.

더보기

forEach는 Consumer<T>를 매개변수로 받으며,
Consumer는 하나의 추상 메서드(accept)를 가진 함수형 인터페이스다.
따라서 우리는 람다식을 통해 accept 메서드의 구현을 정의하고,
그 구현을 forEach에 전달함으로써 데이터 처리 로직과 처리 방식을 분리한다.

하다 보면 늘겠지모,,,

+ Recent posts