Java

Java Stream & Function API

goodbye 2022. 12. 21. 17:46

함수형 프로그래밍(Functional Programming)


함수형 프로그래밍

  • 함수형 프로그래밍은 하나의 프로그래밍 패러다임으로 정의되는 일련의 코딩 접근 방식이며, 자료처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임 을 의미한다.

  • 명령형 프로그래밍 vs 선언형 프로그래밍
    명령형 프로그래밍선언형 프로그래밍
    Imperative ProgrammingDeclarative Programming
    OOP 객체 지향 프로그래밍Functional Programming
    How to do?What to do?
    어떻게 하여야 하는가?무엇을 하여야 하는가?

  • 유저 리스트가 주어졌을때, 검증되지 않은 유저들의 이메일을 리스트로 전달하라는 내용
    • 명령형 프로그래밍 : How to do ?
      1. 이메일을 담을 리스트 선언
      1. 루프
      1. 유저선언
      1. 검증되지 않았는지 체크
      1. 않았다면 변수에 이메일 추출
      1. 이메일 리스트에 넣기

    • 선언형 프로그래밍 : What to do?
      1. 유저리스트에서
        1. 검증되지 않은 유저만 골라내서
        1. 이메일을 추출해서
      1. 리스트로 받기

  • 1급 시민으로서의 함수 : Function as First-Class Citizen
    • 1급 시민의 조건
      1. 함수 / 메서드의 매개변수(parameter)로서 전달할 수 있는가
      1. 함수 / 메서드의 반환값(return)이 될 수 있는가
      1. 변수에 담을 수 있는가

  • 함수형 프로그래밍을 통해 얻을수 있는것
    • 역할에 충실한 코드
      1. 가독성이 좋은 코드
      1. 유지 / 보수에 용이
      1. 버그로부터 안전
      1. 확장성에 용이
    • 패러다임의 전환
      1. Stream, Optional …
      1. 다양한 가능성

Function Interface


  • Function Interface - 1등 시민이 된 함수
  • T → Input Type
  • R → Return Type

@FunctionalInterface
public interface Funtion<T, R>
	R applay(T t);
}

  • 함수를 오브젝트 형태로 구현 예

  • 함수의 구성내용
    1. 함수의 이름
    1. 반환값의 타입 (return type)
    1. 매개변수 (parameters)
    1. 함수의 내용 (body)

  • Lambda Expresstion
    • 위에서 작성했던 Object 함수 클래스를 람다 표현식으로 표현하면 아래와 같다
    (Integer x) -> {
    	return x + 10;
    }		

  • Lambda Expression Simplely
    1. 매개변수의 타입이 유추 가능할 경우 타입 생략 가능
    1. 매개변수가 하나일 경우 괄호 생략 가능
    1. 바로 리턴하는 경우 중괄호 생략 가능

BiFunction Interface


  • 매개변수가 두개일때 사용
  • T, U → 두개의 Input Parameter Type
  • R → Return Type

@FunctionalInterface
public interface BiFunction<T, U, R> {
	R apply(T t, U u);
}

Functional Interface : 함수의 뼈대


  • 단 하나의 abstract method 만을 가지는 인터페이스 : Single Abstract Method Interface
  • Default method 와 static method 는 이미 구현되어 있으므로 있어도 괜찮다
    • java.lang.Runnable
    • java.util.Comparator
    • java.util.concurrent.Callable
    • etc…
@FunctionalInterface
public interface Function<T, R> {
	R apply(T t);   // abstract method

	default <V> Function<V, R> compose(Function<? super V, ? extends T> before{
		...
	}
	default <V> Function<T, V> andThen(Function<? super R, ? extends V> after{
		...
	}
	static <T> Function<T, T> identity() {
		...
	}
}

  • 매개변수 3개를 받는 Functional Interface 구현

Functional Interface : 메서드 종류


Supplier : Input 파라미터 없이 Return 만 하는 메서드 (아낌없이 주는 나무)

@FunctionalInterface
public interface Supplier<T>{
	T get();
}

Consumer : Input 파라미터만 있고 Retrun 하지 않는 메서드 (먹보)

@FunctionalInterface
public interface Consumter<T>{
	void acept(T t);} 

구현 예

  • Consumer 를 사용한 메서드의 변경없이 넘기는 인자값만 변경하면 다른 결과값 출력
  • Parameter 타입을 <T> 로 받으면 다른 자료형의 매개변수도 출력 가능

BiComsumer : 두개의 Input Parameter 를 가지고 리턴하지 않는 추상메서드 제공

Pridicate : True or False 값 (Boolean) 을 리턴

  • negate() : 해당 조건을 만족하지 않는
  • or() : 해당 조건을 만족하는 값도 포함
  • and() : 해당 조건을 만족하는 값만 포함

Comparator : 비교를 위한 인터페이스

Method Reference


1. 클래스의 static method 를 지정할때 : ClassName::staticMethodName

  • 사용예
    Function<String, Integer> strToInt = Integer::parseInt;
    int five = strToInt.apply("5");

BiFunction 인터페이스를 이용하여 아래와 같이 구현해볼수 있다

2. 객체의 instace method 를 지정할때 : ClassName::instanceMethodName

  • 객체를 생성하고 해당 객체의 메서드를 호출할때 아래와 같이 사용할수 있다

  • 해당 클래스의 메서드를 사용하려는 경우 this 를 통해 접근도 가능하다

3. 선언된 객체의 instance method 를 지정 : objectName::instaceMethodName

  • 해당 클래스의 인스턴스를 매개변수로 넘겨 메서드를 실행해주는 함수

    a. 사용예

    Function<String, Integer> strLength = String::length;
    int length = strLength.apply("Hello world");
    
    BiPredicate<String, String> strEquals = String::equals;
    boolean result = strEquals.test("hello", "world");

4. 클래스의 constuctor 를 지정할때 : ClassName::new

해당 클래스의 생성자를 호출하는 예

  1. Car 라는 추상 클래스를 먼저 정의

  1. Car 추상클래스를 상속받는 3개의 클래스 추가

  1. Class::new 를 통해서 생성자를 호출하여 2차원 배열의 값들을 꺼낸다
    • IF - Else 가 반복되지 않고 코드가 간결해질수 있음

Stream


  • 데이터의 흐름
  • 컬렉션(Colllection) 형태로 구성된 데이터를 람다를 이용해 간결하고 직관적으로 프로세스
  • For, While 등을 잉요하던 기존 Loop 대체
  • 손쉽게 병렬 처리 가능

Filter


  • 만족하는 데이터만 걸러내는데 사용
  • Predicate 에 true 를 반환하는 데이터만 존재하는 stream 을 리턴한다
Stream<T> filter(Predicate<? super T> predicate);

  • 검증된 유저만 출력 : isVerified → true

  • 검증되지 않은 유저만 출력 : isVerified → false

  • 에러상태의 주문만 출력 : status → ERROR

Map


  • 데이터를 변형하는데 사용
  • 데이터에 해당 함수가 적용된 결과무을 제공하는 Stream 리턴
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

  • stream 은 재사용할수 없다
  • 재사용하려고 하면 아래와 같이 에러가 발생한다

  • 유저의 이메일만 출력

  • 유저의 생성아이디를 중복제거하여 출력

구성요소


  • Source : 컬렉션, 배열 등
  • Intermediate Operations : 0 개 이상의 filter, map 등 중간처리
  • Terminal Operation : 종결처리 - collect, reduce

  • 인증되지 않은 유저의 이메일 정보만 출력

  • Error 상태의 오더중에서 24시간 이내에 생성된 주문정보만 출력

Error 상태의 2건 정보 중에서 1시간전으로 입력된 정보만 출력되고 72시간 이전으로 입력된 정보는 출력되지 않는것 확인

정렬


  • 데이터가 순서대로 정렬된 상태로 stream 리턴
  • 데이터의 종류에 따라 Comparator 필요
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

  • 유저를 이름순으로 정렬

  • 오더를 생성시간순으로 정렬

중복제거


  • 중복되는 데이터가 제거된 stream 리턴
Stream<T> distinct();

  • 숫자 중복 제거

  • 주문정보 중 유저아이디를 중복제거하고 정렬

FlatMap


  • Map + Flatten
  • 데이터에 함수를 적용한 후 중척된 stream 을 연결하여 하나의 stream 리턴
<R> Stream<R> flatMap (
	Function<? super T,
	? extends Stream<? extends R>> mapper);

  • 중첩된 배열 데이터를 하나의 리스트로 변환하려고 할때
  • map 으로 스트림 리스트를 반환하면 원하는 결과를 얻을수 없다

  • faltMap() 메서드를 이용해서 리턴하면 아래와 같이 하나의 리스트로 반환된다

  • 오더정보의 오더라인 리스트 데이터들을 하나의 리스트로 출력


Uploaded by N2T