본문 바로가기

우아한테크코스

람다와 스트림

함수형 프로그래밍


  • 함수
    • 함수는 같은 인수값으로 함수를 호출했을 때 항상 같은 값을 반환한다. (Random, Scanner 등은 함수가 될 수 없다)

  • 함수형 프로그래밍
    • 함수나 메서드는 지역 변수만을 변경해야 함수형이라 할 수 있다.
      • side-effect가 발생하지 않음
    • 그리고 함수나 메서드에서 참조하는 객체가 있다면 그 객체는 불변객체여야 한다. 즉, 객체의 모든 필드가 final이어야 하고 모든 참조 필드는 불변 객체를 직접 참조해야 한다.
    • 함수나 메서드가 어떤 예외도 일으키지 않아야 한다.

  • 이점
    • 멀티코어와 동시성 제어
    • 캐싱
      • 입력에 따른 출력 결과가 같기 때문에 캐싱을 하여 다시 계산하지 않고 저장하는 최적화 기능을 제공한다.
    • 꼬리호출 최적화

람다표현식


  • 람다표현식
    • 람다 표현식은 메서드로 전달할 수 있는 익명 함수이며, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트 등을 가질 수 있다.

함수형인터페이스


단 하나의 추상 메소드만이 선언된 인터페이스이다. 함수형 인터페이스라는 개념과 람다식 표현을 통해, 입력에 의해서만 출력이 결정되어 Java8에서는 Side-effect가 없는 ‘순수한 함수’를 표현할 수 있게 되었다. 하지만, 단순히 구조만으로 순수성이 보장되지는 않는다. 입력에 참조값이 오는 경우 Side-effect가 생긴다.

Functional Interface 종류

기타


  • foreach 에서 연산하지 마라
    • stream api의 foreach는 가급적 출력 용도로만 활용하여야 한다. 로직을 수행할 거면 반복문을 사용하는 것이 객체지향적인 설계를 가능케 한다고 생각한다. foreach에서 로직이 수행될 경우 동시성 보장이 어려워지고 가독성이 떨어져 effective java item 46에서는 주의해서 사용하길 권하고 있다. 추가적으로, stream api는 지연연산을 하므로 일반적인 loop와 다르게 동작하므로 결과가 예측과 다를 수 있다.

예제1


다음코드를 람다와 스트림을 적용해 리펙토링하기

 public class Calculator {
    public static int sumAll(List<Integer> numbers) {
        int total = 0;
        for (int number : numbers) {
            total += number;
        }
        return total;
    }


    public static int sumAllEven(List<Integer> numbers) {
        int total = 0;
        for (int number : numbers) {
            if (number % 2 == 0) {
                total += number;
            }
        }
        return total;
    }


    public static int sumAllOverThree(List<Integer> numbers) {
        int total = 0;

        //TODO: List에 담긴 값 중 3보다 큰 수만을 더해야 한다.

        return total;
    }
}

다음과 같이 바꿀 수 있다.

public class Calculator {
    public static int sumAll(List<Integer> numbers) {
        return sumSteam(numbers, x -> true);
    }

    public static int sumAllEven(List<Integer> numbers) {
        return sumSteam(numbers, x -> x % 2 == 0);
    }

    public static int sumAllOverThree(List<Integer> numbers) {
        return sumSteam(numbers, x -> x > 3);
    }

    public static int sumSteam(List<Integer> numbers, Predicate<Integer> predicate) {
        return numbers.stream()
                .filter(predicate)
                .reduce(0, Integer::sum);
    }
}

예제2

CarTest에서 MoveStrategy에 대한 익명 클래스로 구현하고 있는데 람다를 적용해 구현한다.

class CarTest {
    @Test
    public void 이동() {
        Car car = new Car("pobi", 0);
        Car actual = car.move(new MoveStrategy() {
            @Override
            public boolean isMovable() {
                return true;
            }
        });
        assertThat(actual).isEqualTo(new Car("pobi", 1));
    }

    @Test
    public void 정지() {
        Car car = new Car("pobi", 0);
        Car actual = car.move(new MoveStrategy() {
            @Override
            public boolean isMovable() {
                return false;
            }
        });
        assertThat(actual).isEqualTo(new Car("pobi", 0));
    }
}

MoveStrategy() 의 모습

public interface MoveStrategy {
    boolean isMovable();
}

Car.move() 의 모습

public Car move(MoveStrategy moveStrategy) {
        if (moveStrategy.isMovable()) {
            return new Car(name, position + 1);
        }
        return this;
    }

다음과 같이 바꿀수 있다.

class CarTest {
    @Test
    public void 이동() {
        Car car = new Car("pobi", 0);
        Car actual = car.move(() -> true);
        assertThat(actual).isEqualTo(new Car("pobi", 1));
    }

    @Test
    public void 정지() {
        Car car = new Car("pobi", 0);
        Car actual = car.move(() -> false);
        assertThat(actual).isEqualTo(new Car("pobi", 0));
    }
}
반응형

'우아한테크코스' 카테고리의 다른 글

준 강의 - 프론트엔드 기본 꿀팁  (0) 2020.05.15
api 설계  (0) 2020.05.12
데이터 베이스  (0) 2020.04.24
테스트하기 어려운 코드  (0) 2020.03.31
클래스와 인스턴스  (0) 2020.03.17