Post

Java Standard - 람다와 스트림

...

Java Standard - 람다와 스트림

람다식

람다식이란?

  • 정의
    • 메서드를 하나의 식으로 표현한 것
    • 함수를 간략하면서도 명확한 식으로 표현할 수 있게 함
    • 익명함수: 메서드의 이름과 반환값이 없어지므로
  • 특징
    • 원래는 클래스 정의 후 객체 생성을 해야지만 메서드를 호출 할 수 있지만 람다식은 생략함
    • 메서드를 변수처럼 다루는 것이 가능: 매개변수나 메서드의 결과로 반환 가능
메서드와 함수의 차이
함수
함수는 수학에서 따온 이름 메서드
객체의 행위나 동작을 의미 함수와 같은 의미지만 특정 틀래스에 반드시 속해야한다는 제약이 있음 하지만 람다식을 통해 메서드가 하나의 독립적인 기능을 하기 때문에 함수라는 용어를 다시 사용함

람다식 작성하기

  • 작성방법

    Build source

    • 제거: 이름과 반환타입
    • 추가: → (메서드 선언부와 바디 사이에 위치)
  • 변형 조건

    • 반환 값이 있는 메서드 : return문 → 식
    • 매개변수의 타입을 추론 가능한 경우: 매개변수의 타입 생략 가능
    • 반환타입이 없는 경우: 반환타입 생략 가능
    • 매개변수가 하나뿐인 경우: 괄호()를 생략
    • 문장이 하나뿐인 경우: 괄호{}를 생략

함수형 인터페이스

함수형 인터페이스

  • 의미: 람다식을 다루기 위한 인터페이스
  • 특징
    • 오직 하나의 추상메서드만 정의 되어있어야함
    • @FunctionalInterface 를 붙여 컴파일러가 함수형 인터페이스를 올바르게 정의했는지 확인
    • 람다식과 동등한 메서드가 정의 되어야함
    • 매개변수의 선언부와 반환값이 일치해야함
    • default메서드와 static메서드의 개수에는 제약이 없음
  • 인터페이스로 다루는 이유

    람다식은 익명 클래스의 객체와 동등

    익명 클래스의 메서드를 사용하기 위해서는 참조변수에 저장을 해야함

    이때 어울리는 참조변수로는 클래스 또는 인터페이스이여야함

    메서드의 내용은 람다식으로 사용하니, 인터페이스만 있으면 되므로 인터페이스로 다룸

함수형 인터페이스 타입의 매개변수와 반환타입

  • 함수형 인터페이스 타입의 매개변수
    • 람다식을 참조하는 참조변수를 매개변수로 지정해야함
    • 참조변수 없이 람다식을 매개변수로 지정하는 것도 가능
  • 함수형 인터페이스 타입의 반환타입
    • 람다식을 가리키는 참조변수를 반환
    • 람다식을 직접 반환
  • 람다식의 타입

    함수형 인터페이스로 람다식을 참조할 수 있는것, 함수형 인터페이스 타입 ≠ 람다식 타입

    람다식 = 익명객체, 익명객체 → 타입을 컴파일러가 정하기 때문에 알 수 없음

    ⇒ 대입 연산자 이용시 형변환이 필요 MyFunction f = (MyFunction) (()->{});

  • 람다식의 형변환

    람다식을 함수형 인터페이스가 아닌 다른 타입으로 바로 형변환 하지 못함

    람다식 → 함수형 인터페이스 → 다른 타입 의 순서로 가능

  • 외부 변수 참조

    외부에서 선언된 변수라도, 람다식 내에서 참조하는 지역변수는 상수로 간주

java.util.function 패키지

기본적인 @functional Interface

  • 의미: 함수형 인터페이스를 일반적으로 자주 쓰이는 형식으로 미리 정의해놓은 패키지
  • 종류

    Build source

    • Predicate: 조건식을 람다식으로 표현하는데 사용, 반환타입이 boolean, Function의 변형

다양한 @functional Interface

매개변수가 2개

  • 특징: 이름에 접두사 Bi가 붙음
  • 종류

    Build source

매개변수와 반환타입이 일치

  • 특징: Function의 변형
  • 종류

    Build source

기본형을 사용

  • 특징: wrapper클래스가 아닌 기본형을 사용하는 함수형 인터페이스
  • 종류

    Build source

    이때의 A, B는 각 기본형 데이터 타입이 대입됨

컬렉션 프레임웍과 @functional Interface

  • 특징
    • 컬렉션 프레임웍의 인터페이스에 default 메서드가 추가되었는데, 일부는 함수형 인터페이스를 사용함
  • 종류

    Build source

Function의 합성과 Predicate의 결합

functional Interface에는 다양한 default 메서드와 static 메서드가 있는데, 2개의 functional interface만 살펴볼 예정 → 나머지 클래스도 유사하기 때문

  • Function
    • andThen(): 두 함수의 실행 순서를 지정, a.andThen(b) = a → b

      1
      2
      3
      
        Function<**String**, Integer> f = (s) -> Integer.parseInt(s, 16);
        Function<Integer, **String**> g = (i) -> Integer.toBinaryString(i);
        Function<**String**, **String**> h = f.andThen(g);
      

      Build source

    • compose(): 두 함수의 실행 순서를 지정, a.andThen(b) = b → a

      1
      2
      3
      
        Function<**Integer**, String> g = (i) -> Integer.toBinaryString(i);
        Function<String, **Integer**> f = (s) -> Integer.parseInt(s, 16);
        Function<**Integer**, **Integer**> h = f.compose(g);
      

      Build source

    • identity(): 항등 함수가 필요할 때 사용, map()으로 변환 작업시 사용

  • Predicate
    • and(): 조건식 논리 연산자&&와 같은 역할
    • or(): 조건식 논리 연산자||와 같은 역할
    • negate(): 조건식 논리 연산자!와 같은 역할
    • isEqual(): 두 대상을 비교하는 Predicate를 만들 때 사용, 비교 대상 하나를 지정하고 나머지 하나는 test()에 지정

메서드 참조

  • 의미: 람다식을 간략하게 하는 방법, 하나의 메서드만 호출하는 람다식을 간략하게 하는 방법, 람다식을 마치 static 변수처럼 다룰 수 있도록 해줌
    • 클래스이름::메서드이름 참조변수::메서드이름
  • 특징
    • 람다식이 하나의 메서드만 호출하는 경우에만 가능
    • 람다식의 내용이 생략되어도 컴파일러는 1. 함수형 인터페이스의 타입이나 2. 사용되는 메서드의 선언부로 추정 가능
    • 메서드 뿐만 아니라 생성자도 가능함
  • 예시
    • 메서드

      Build source

    • 생성자

      • Supplier<MyClass> s = () -> new MyClass();Supplier<MyClass> s = MyClass::new;
      • Function<MyClass> s = (i) -> new MyClass(i);Function<MyClass> s = MyClass::new;
      • BiFunction<MyClass> s = (i, s) -> new MyClass(i, s);BiFunction<MyClass> s = MyClass::new;
      • Function<MyClass> s = x -> new int[x];Function<MyClass> s = int[]::new;

스트림

스트림이란?

  • for과 Iterator의 단점
    • 코드가 너무 길어짐
    • 알아보기 어려움
    • 재사용성이 떨어짐
    • 데이터 소스마다 다른 방식으로 바꿔야함
  • Stream의 배경: for과 Iterator의 단점 극복
    • 장점
      • 데이터의 소스를 추상화
        • 데이터 소스가 무엇이던간에 같은 방식으로 다룰 수 있도록 함
        • 코드의 재사용성이 높아짐
      • 자주 사용되는 메서드들 정의
    • 특징
      • 데이터 소스를 변경하지 않음

        데이터 소스로부터 데이터를 읽기만 하고, 변경하지 않음

      • 스트림은 일회용

        스트림은 사용하고 나면 닫혀서 다시 사용할 수 없음

      • 스트림은 작업을 내부반복으로 처리함

        내부반복: 반복문을 메서드의 내부에 숨길 수 있는 것

      • 지연된 연산
      • 병렬 스트림

스트림의 연산

  • 중간 연산: 결과가 스트림, 연속해서 중간 연산할 수 있음

    Build source

  • 최종 연산: 결과가 스트림이 아님, 단 한번만 가능(스트림을 소모함)

    Build source

지연된 연산

최종 연산이 수행되기 전에는 중간 연산이 수행되지 않음

순서는 중간연산 → 최종연산 인데, 최종연산이 결정되지 않으면 중간연산의 작업도 진행하지 않음

병렬 스트림

스트림은 병렬처리가 쉬운데, 내부적으로 fork&join프레임웍을 사용해 병렬로 수행

기본적으로 병렬처리 X, 병렬 처리를 원하면 sequential() 호출, 취소하려면 paraller() 호출

스트림 만들기

스트림의 소스가 될 수 있는 대상: 배열, 컬렉션, 임의의 수 등 다양함

소스별 만드는 방법

  • 컬렉션
    • Collection static 메서드 Stream<T> Collection.stream()
  • 배열
    • Stream static 메서드 Stream<T> Stream.of(T[])
    • Arrays static 메서드 Stream<T> Arrays.sream(T[])
  • 특정 범위의 정수
    • IntStream static 메서드 IntStream IntStream.range(int begin, int end)
    • LongStream static 메서드 LongStream LongStream.range(int begin, int end)
  • 임의의 수
    • Random 메서드 ints()
    • Random 메서드 longs()
    • Random 메서드 doubles()

    → 무한 스트림이므로 limit()으로 스크림의 크기제한이 필요

람다식 - iterate(), generate()

  • Stream 메서드 iterate()
    seed값 부터 시작해, 람다식으로 계산을 반복, 이전 값 이용
  • Stream 메서드 generate()
    람다식으로 계산을 반복, 이전 값 이용하지 않음, 매개변수가 없는 람다식 이용

    → 무한 스트림이므로 limit() 으로 스트림의 크기 제한이 필요

  • 파일
    • Files static 메서드 Stream<Path> Files.List(Path dir)
  • 빈스트림
    • Stream 메서드 Stream Stream.empty()
  • 두 스트림 연결
    • Stream static 메서드 Stream<T> Stream.concat(Stream<T>)

스트림의 중간연산

  • 자르기
    • skip() : 처음 n개의 요소를 건너뛰고 잘라냄
    • limit() : 스트림의 요소를 n개로 제한함
  • 요소 걸러내기
    • filter(): 스트림에서 중복된 요소들을 제거
    • distinct() : 주어진 조건에 맞지 않는 요소를 걸러냄
  • 정렬
    • sorted(): 스트림을 Comparator를 이용해 정렬함, Comparator대신 int값을 리턴하는 람다식으로도 가능
  • 변환
    • map(): 원하는 필드만 뽑아내거나 특정 형태로 변환 할 때 사용,매개변수로 변환해서 반환하는 함수를 지정해야함
  • 조회
    • peek(): 연산과 연산 사이에 올바르게 처리가 되었는지 확인하고 싶을때 사용

기본형 스트림

  • 종류: IntStream, DoubleStream, LongStream
  • 메서드(최종연산)
    • sum(): 스트림의 모든 요소의 총합
    • average(): sum() / (double) count()
    • max(): 스트림의 요소 중 제일 큰 값
    • min(): 스트림의 요소 중 제일 작은 값

flatMap()

스트림의 요소가 배열이거나, map()의 연산 결과가 배열인 경우 = 스트림의 타입이 Stream<T[]> 인 경우

map() 대신 flatMap()을 사용하면 된다.

  • map() 사용시

    Build source map() 사용시

  • flatMap() 사용시

    Build source flatMap() 사용시

  • map()flatMap() 비교

    Build source

Optional와 OptionalInt

Optional

1
2
3
4
public final class Optional<T>{
	private final T value; // T타입의 참조변수
	...
}
  • 의미: 지네릭 클래스로 T타입의 객체를 감싸는 래퍼 클래스, 모든 타입의 참조변수를 담을 수 있음
  • 장점
    • 반환시 Optional객체에 담아 반환한다면, 반환 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해 간단히 처리 할 수 있음
  • 생성
    • of(): 값 생성
    • ofNullable() : 값이 null일 가능성이 있으면, 사용하는 값 생성 메서드
    • empty(): 기본값으로 초기화
  • 값 가져오기
    • get(): 저장된 값 조회
    • orElse(): 값이 null이라면, 대체될 값 지정
      • orElseThrow(): orElse() 변형, 값이 null이면 예외를 발생
      • orElseGet(): orElse()변형, 값이 null이면 반환되는 값을 람다식으로 지정
  • 이외의 메서드
    • filter()
    • map()
    • flatMap()
    • isPresent(): Optional객체의 값이 null이면 false, 아니면 true를 반환
    • ifPresent(): 값이 있으면 주어진 람다식을 실행하고, 없으면 아무일도 하지 않음

OptionalInt, OptionalLong, OptionalDouble

기본형을 값으로 하는 Optional

  • 특징
    • int의 기본값인 0을 저장하는 것과 empty()으로 아무런 값을 갖지 않는 것은 다른 의미!
    • Optional객체에 null을 저장하면, 비어있는 것과 동일한 취급

스트림의 최종연산

최종연산은 스트림을 소모해 결과를 만들어냄, 떄문에 최종 연산 후에는 스트림이 닫혀 더이상 사용할 수 없음

최종 연산의 결과는 단일 값, 배열 또는 컬렉션 이다.

  • 반복
    • foreach(): 반환 타입이 void이며, 스트림의 요소를 출력하는 용도로 많이 쓰임
  • 조건검사

    스트림의 요소에 대해 지정된 조건에 일치하는지 확인하는데 사용하는 메서드

    매개변수로 Predicate를 요구하며, 연산 결과로 boolean을 반환

    • allMatch(): 전체 일치
    • anyMatch(): 일부 일치
    • noneMatch(): 불일치
    • findFirst(): 조건에 일치하는 요소 중 첫번째 요소
    • findAny(): 조건에 일치하는 요소 중 아무 순서의 요소, 병렬 스트림의 경우에는 이 메서드를 사용해야함
  • 통계
    • 기본형 스트림이 사용할 수 있는 메서드: count(), sum(), average(), max(), min()
    • 기본형이 아닌 스트림이 사용할 수 있는 메서드: count(),max(), min()
  • 리듀싱
  • reduce(): 스트림의 요소를 줄여가면서 연산을 수행한 후 모든 요소를 소모하면 그 결과를 반환

매개변수로 받은 람다식을 요소를 하나씩 소모해가면서? 하는 건가?? 이런식으로 sum()? 만든건가

collect()

스트림의 요소를 수집하는 최종연산

스크림의 요소 수집에 대한 방법을 매개변수로 받는데, 이것을 컬렉터 라고 하고 함

Collectors는 Collector인터페이스를 구현한 것

스트림을 컬렉션과 배열로 변환

스트림의 모든 요소를 컬렉션에 수집하려면, 메서드를 이용하고, 특정 컬렉션을 지정하려면 toCollection()에 해당 컬렉션의 생성자 참조를 매개변수로 넣어주면 됨

  • 메서드: toList(), toSet(), toMap(), toCollection(), toArray()

통계

  • 메서드: counting(), summing(), averagingInt(), maxBy(), minBy()

리듀싱

IntStream에는 매개변수 3개짜리 collect()만 있으므로 boxed()를 통해 Stream로 변환해야 사용 할 수 있음

  • 메서드: reducing()

문자열 결합

문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환, CharSequence의 자손인 경우에 결합이 가능, 스트림의 요소가 문자열이 아닌경우 map()을 이용해 스트림의 요소를 문자열로 변환해야함, 그렇지 않으면 toString()의 호출 결과를 결합

  • 메서드: joining()

그룹화와 분할

  • 의미
    • 그룹화: 스트림의 요소를 특정 기준으로 그룹화하는 것
    • 분할: 요소를 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로의 분할
  • 메서드
    • groupingBy(): 분류를 Function으로 함, 결과는 Map
    • partitioningBy(): 분류를 Predicate로 함, 더 빠름, 결과는 Map

Collector구현하기

Collector

1
2
3
4
5
6
7
8
9
public interface Collector<T, A, R>{
	Suppier<A> supplier();
	BiConsumer<A, T> accumulator();
	BinaryOperator<A> combiner();
	Function<A, R> finisher();

	Set<Characteristics> characteristics();
	...
}
  • 직접 구현해야하는 메서드
    • supplier(): 작업 결과를 저장할 공간을 제공
    • accumulator(): 스트림의 요소를 수집할 방법을 제공
    • combiner(): 두 저장공간을 병합할 방법을 제공(병렬 스트림)
    • finisher(): 결과를 최종적으로 변환할 방법을 제공, 변환이 필요 없으면 항등함수인 Function.identity()를 반환
    • characteristics(): 컬렉터가 수행하는 작업의 속성에 대한 정보를 제공
      • characteristics.CONCURRENT: 병렬로 처리할 수 있는 작업
      • characteristics.UNORDERED: 스트림의 요소의 순서가 유지 될 필요가 없는 작업
      • characteristics.IDENTITY_FINISH: finisher()가 항등 함수인 작업

스트림의 변환

Build source Build source

This post is licensed under CC BY 4.0 by the author.