210106 람다식과 스트림

2021-01-06

람다식

(매개변수)->{실행문;}
  • 자바 8부터 함수형 프로그래밍 방식을 지원

함수형 프로그래밍

  • 함수를 기반으로 자료를 입력받아 구현한다
  • 함수의 구현과 호출만으로 프로그램을 만들 수 있다
  • 순수 함수(pure function)을 구현, 호출하여 외부 자료에 부수적인 영향을 주지 않도록 구현한다
    • 순수 함수: 매개변수만을 사용하여 만드는 함수
    • 함수 내부에서 함수 외부에 있는 변수를 사용하지 않아, 함수가 수행되더라도 외부 자료에는 영향이 없다

함수형 프로그래밍의 장점

  • 여러 자료를 동시에 처리하는 병렬 처리에 적합하다
    • 함수가 입력받은 자료 외 외부 자료에 영향을 미치지 않기 때문
  • 안정되고 확장성 있는 프로그램을 개발할 수 있다
  • 함수 기능이 자료에 독립적임을 보장한다
    • 동일한 입력에 대해 동일한 출력 보장
    • 다양한 자료에 같은 기능을 수행할 수 있다

람다식 문법

  • 함수 이름이 없는 익명 함수를 만든다
// 1. 매개변수 자료형을 생략 가능하며, 매개변수가 하나인 경우에는 괄호도 생략 가능하다. 
str -> {System.out.println(str);}
(x,y) -> {System.out.println(x+y);}

// 2. 중괄호 내부의 구현 부분이 한 문장인 경우, 중괄호를 생략할 수 있다
// 하지만 return문은 중괄호를 생략할 수 없다
str -> System.out.println(str);
str -> { return str.length();}

// 3. 중괄호 안의 구현 부분이 return문 하나라면, 중괄호와 return을 모두 생략하고 식만 쓴다
(x , y)-> x + y;
str -> str.length();

람다식 사용

// 함수형 인터페이스 선언
public interface MyNumber {
    int getMax(int num1, int num2);
}
// 람다식 구현 및 호출
public class TestMyNumber {
    public static void main(String[] args){
        MyNumber Max = (x,y) -> (x>=y) ? x : y;
        System.out.println(max.gerMax(10,20));
    }
}

함수형 인터페이스

  • 람다식은 메서드 이름이 없다
    • 메서드를 실행하는데 필요한 매개변수와 매개변수를 활용한 실행 코드를 구현
  • 자바에서는 참조 변수 없이 메서드를 호출할 수 없다
  • 람다식을 구현하기 위해 함수형 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언한다.
  • 람다식은 오직 하나의 메서드만 선언한 인터페이스를 구현할 수 있다
    • 하나의 메서드를 구현하여 인터페이스형 변수에 대입
    • 이름이 없는 익명 함수로 구현하기 때문에 인터페이스에 메서드가 여러 개 있다면 어떤 메서드를 구현한 것인지 모호해진다

@FunctionalInterface 애너테이션

  • 함수형 인터페이스라는 의미를 명시적으로 표현한다

  • 람다식으로 구현한 인터페이스에 실수로 다른 메서드를 추가하는 것을 막는다

익명 객체

  • 익명 내부 클래스는 클래스 이름 없이 인터페이스 자료형 변수에 바로 메서드 구현부를 생성하여 대입할 수 있다
  • 람다식 코드에서 외부 메서드의 지역 변수를 수정할 수 없다
    • 지역 변수는 메서드 호출이 끝나면 메모리에서 사라진다
    • 익명 내부 클래스에서 사용하는 경우에는 지역 변수가 상수로 변한다
    • 람다식 역시 익명 내부 클래스가 생성, 외부 메서드의 지역 변수를 사용하면 변수는 상수(final)가 되어 오류 발생

함수를 변수처럼 사용하는 람다식

interface PrintString{ 
	void showString(String str);
}
public class TestLambda{ 
	public static void showMyString(PrintString p){
    	p.showString("Hello2");
	}
    public static PrintString returnString(){
        /*
        PrintString str = s->System.out.println(s+" world");
        return str;
        */
        return s->System.out.println(s+" world");
    }
    public static void main(String args[]){
        // 1) 인터페이스형 변수에 람다식 대입
    	PrintString lambdaStr = s-> System.out.println(s);
	    lambdaStr.showString("Hello");   
        // 2) 함수의 매개변수로 람다식 전달
        showMyString(lambdaStr);
        // 3) 반환값으로 람다식 사용
        PrintString reStr=returnString();
        reStr.showString("Hello3");
    }
}


스트림

int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(n->System.out.println(n));
  • 여러 자료의 처리에 대한 기능을 구현해놓은 클래스
  • 배열, 컬렉션 등의 자료를 일관성있게 처리할 수 있다
    • 처리해야 하는 자료가 무엇인지와 상관없이 같은 방식으로 메서드를 호출한다
      • 자료를 추상화했다

스트림 연산

중간 연산

  • 자료를 거르거나 변경하여 또 다른 자료를 내부적으로 생성
filter()
sList.stream().filter(s -> s.length() >= 10).forEach(s -> System.out.println(s));
  • 조건을 넣고 true인 경우에만 자료 추출
map()
customerList.stream().map(c->c.getName()).forEach(s -> System.out.println(s))
  • 클래스가 가진 자료 중 한가지만 추출
  • 요소들을 순회하면서 다른 형식으로 변환하는 경우에도 사용

최종 연산

  • 생성된 내부 자료를 소모해 가면서 연산을 수행
  • 마지막에 한 번만 호출된다
  • 최종 연산이 수행되고 나면 해당 스트림은 더이상 사용할 수 없다
forEach()
  • 요소를 하나씩 꺼낸다
count()
int count = (int) Arrays.stream(arr).count();
  • 통계: 배열 요소의 개수를 출력
sum()
int sum = Arrays.stream(arr).sum();
  • 통계: 배열 요소의 합계를 구함

Collection에서 스트림 생성/사용

public class ArrayListStreamTest{
    public static void main(String[] args){
        List<String> sList = new ArrayList<>();
        sList.add("A");
        sList.add("B");
        sList.add("C");
        
        Stream<String> stream = sList.stream();
        stream.forEach(s -> System.out.println(s));
        System.out.println();
        sList.stream().sorted().forEach(s -> System.out.println(s));
    }
}
  • Collection 인터페이스의 stream() 메서드 사용, 스트림 생성
    • 이 스트림은 내부적으로 해당 Collection 구현체의 모든 요소를 가지고 있다
sorted()
  • 요소의 정렬을 위한 중간 연산
  • 사용하는 자료 클래스가 Comparable 인터페이스를 구현해야 한다
  • 만약 구현되어 있지 않다면 메서드의 매개변수로 Comparator 인터페이스를 구현한 클래스를 지정할 수 있다

스트림의 특징

자료의 대상과 관계없이 동일한 연산을 수행한다

  • 컬렉션의 여러 자료 구조에 대해 요소 출력, 조건에 따른 자료 추출, 합계나 평균 등의 작업을 일관성 있게 처리할 수 있는 메서드를 제공한다

한 번 생성하고 사용한 스트림은 재사용할 수 없다

  • 스트림의 요소들은 최종 연산시 ‘소모된다’
  • 소모된 요소들은 재사용할 수 없으며, 다른 기능을 호출하고 싶다면 스트림을 새로 생성해야 한다

스트림의 연산은 기존 자료를 변경하지 않는다

  • 정렬한다거나 합을 구하는 등의 여러 연산을 수행한다고 해서 기존 배열이나 컬렉션이 변경되지는 않는다
  • 스트림 연산을 위해 사용하는 메모리 공간이 별도로 존재한다

스트림의 연산은 중간 연산과 최종 연산이 있다

  • 중간 연산은 여러 개가 적용될 수 있으며, 최종 연산은 맨 마지막에 한 번 적용된다.
  • 만약 중간 연산이 여러 개 호출되더라도 최종 연산이 호출되어야 스트림의 중간 연산이 모두 적용된다
    • 지연 연산(lazy evaluation)

reduce() 연산

T reduce(T identify, BinaryOperator<T> accumulator)
  • 내부적으로 스트림의 요소를 하나씩 소모하면서 프로그래머가 직접 정의한 기능을 수행한다
  • T identify : 초기값
  • BinaryOperator 인터페이스 : 두 매개변수로 람다식을 구현, 각 요소별 기능 수행
    • 매개변수로 람다식을 직접 작성해도 되고, 길이가 길다면 인터페이스를 구현한 클래스를 생성하여 대입해도 된다
    • 함수형 인터페이스이므로 apply() 메서드를 반드시 구현해야 한다
      • apply() : 두 개의 매개변수와 한 개의 반환값을 가진다(셋 다 같은 자료형)
class CompareString implements BinaryOperator<String> {
    @Override
    public String apply(String s1, String s2){
        if(s1.getBytes().length >= s2.getBytes().length) return s1;                                                 else return s2;
    }
}
public class ReduceTest {
    public static void main(String[] args){
        String[] greetings = {"안녕하세요","hello","반갑습니다"};
        // 1. 매개변수로 람다식 직접 작성하기
        System.out.println(Arrays.stream(greetings).reduce("",(s1,s2)->{
            if(s1.getBytes().length >= s2.getBytes().length) return s1;                                                 else return s2;});
        // 2. BinaryOperator 인터페이스를 구현한 클래스 사용하기                   
        String str = Arrays.stream(greetings).reduce(new CompareString()).get();
        System.out.println(str);
    }
}                        

궁금증: 스트림의 forEach()와 for-loop의 성능 차이?

  • 요소를 순회하기 위해 스트림의 forEach() 최종 연산을 사용할때와 for-loop를 사용할 때 성능 차이가 발생할까?
  • 기본 for-loop을 사용했을 때 가장 빠르다
    • 기본 for-loop은 오버헤드가 없는 단순 인덱스 기반 메모리 접근이므로 스트림보다 빠르다
    • 비교적 최근에 도입된 스트림보다 for-loop의 컴파일러 최적화가 더 정교하다

출처

Do it! 자바 프로그래밍

https://madplay.github.io/post/mistakes-when-using-java-streams