DevBoi

[Java] Functional api (Jdk8 Version up) 본문

Language/[Java]

[Java] Functional api (Jdk8 Version up)

HiSmith 2022. 5. 15. 15:31
반응형

 

자바8에서 사용되는 Funtional api이다.

자바8에서 람다랑 스트림이 추가되었는데, 왜 추가되었을까? 함수형 프로그래밍을 받아들이기 위해서이다.

 

1.람다 표현식

람다의 핵심은 지울수있는건 모두 지우자 이다.

모든걸 컴파일러의 추론에 의지하고, 코드로 표현하는 건 다 없애서, 간결하게 만드는 것을 목표로한다.

 

인터페이스 내 추상메서드가 한개로 정의가 되어있다면, 간략하게 람다식으로 구현을 할수 있다.

 

 

이런식으로 interface에 대한 추상메서드가 한개 존재할때, 해당 식을 람다로 간략하게 표현할 수 있다.

그런데 만약에 인터페이스 내 추상메서드가 2개 이상이라면? 이러한 람다식을 사용할 수 없다.

 

추후 운영 관리를 하다가 만약에 다른 사람이 인터페이스에서 메서드를 한개 추가하면, 해당 기존에 사용하던 람다는 오류가 터질것이고 멘붕쓰.....

 

그.래.서 필요한게 명시!

@FunctionalInterface이다.

해당 어노테이션은 함수형 인터페이스라는 것을 알려준다. 추상 메서드 1개가 아닐 경우에 컴파일러 에러를 뿜어준다.

이는, 추후 운영관리를 위해 꼭 사용하는 것이 좋다.

(만약에 JDK8에서 지원해주는 것 외 인터페이스를 만들어서 사용하는 경우, 해당 인터페이스에 해당 어노테이션을 꼭 추가하자!)

 

 

1) Runnable 

기존부터 존재하던 인터페이스로, 스레드를 생성할때 주로 사용하였으며, 가장 기본적인 함수형 인터페이스다.

void 타입의 인자없는 메서드를 가지고 있다.

어떨때 쓰이고, 뭐가좋을까?

일단, 위에서 말했듯이, Runnable은 메소드 run한개를 가진다. (2개이상이면 람다를 못씀 ㅋ)

Thread를 별도로 구현하면 상속을 통해서 구현해야하고, Runnable은 impl을 통해 구현할 수 있다.

또한 Thread구현체는 단순 선언 및 start를 해주면 되지만, Runnable은 Thread에 Runnable형 인자를 넘겨서, start를 해줘야한다는 차이가 있다.

 

엇? 잠깐 Runnable run은 그러면 뭐임? Thread start로 굳이 써야하는 이유가있나요?

 

* Runnable run은 단일 쓰레드로 한개의 쓰레드에서 동작한다.

즉, Runnable run을 하게 되면, 단일 쓰레드 내에서 동작하게 된다.

근데 쓰레드를 신규로 생성하고 start를 하게 되면, 쓰레드가 생성되고 해당 생성된 쓰레드 내에서 run 메소드를 실행하게 된다.

쉽게 말하면 단일 쓰레드 멀티쓰레드의 차이라고 볼 수 있다.

 

결론 : 쓰레드는 클래스이고, Runnable은 인터페이스이기 때문에 확장성에 필요한 것들을 위해 개발할떄는 해당 Runnable을 사용하여 개발 필요

 

 

 

2)Supplier<T>

인자는 받지 않으며 리턴타입만 존재하는 메서드만 가지고 있다.

 

근데 .. 이걸 보다보니, 그런생각이 든다.

이걸 왜쓰지? 그냥 변수로 출력하면되는데?

심지어, 만약에 Primitive type으로 해야될때는 오토박싱에 의한 오버헤드 발생이 될텐데? 어떨때 쓰는거지?

우선 기본형에 대한 Supplier는 별도로 존재한다.

 

사용 이유 1. Stream의 generate, 해당 api를 쓰게되면, Supplier를 인자로 받아 무한한 스트림을 생성한다.

 

사용이유2. Lazy Evaluation

printValidIndex는 0이상일때만 동작하는 함수이다.

또한 getVeryExpensiveValue는 쓰레드가 3초 sleep을 한다고 생각해보자

하나는 그냥 단순히 String으로 받고, 하나는 Supplier를 통해서 받는다

그러면? 해당 Supplier는 다 실행하는 것이 아니라, 앞의 선조건 0이상인 경우에만 실행되게 된다.

 

이를 지연 계산 이라고한다.

근데 왜? 어떤 이유떄문에 지연계산이 되는걸까?

내 생각. Supplier가 아닌 경우, 함수 실행 전 인자에 대한 값이 전부 있어야 하지만,

Supplier를 사용하는 경우, 값을 바로 가져오지 않고, 실제로 printIfValidIndex 내부에서 supplier의 값을 사용할때 해당 getVeryExpensive가 실행될 것이다. 즉 실제로 사용할때 Supplier는 값을 가져오기 떄문에, 미리 값을 계산하지않고, 값을 사용할때 사용한다.

 

근데 어떻게 이렇게 동작하는걸까? 맞다, 실제로 함수를 실행시킬때 Supplier로 인자가 넘어오게 되면 get으로 실제 값이 필요할때 해당 값을 가져오게끔 한다.

 

 

2. Consumer

 

Consumer는 accept라는 메소드가 추상메서드이고, andThen을 디폴트 인터페이스로 정의한다.

 

디폴트로 되어있는 andThen을 사용하면 연속적으로 사용이 가능하다

andThen은 다음 Consumer에 대한 값으로 accept로 붙여주기 떄문이다.

만약에 이렇게 값이 있다면? printString의 값을 accept 시켜주는 것이다.

또한 반대의 개념은 compose가 있다.

andThen이 a ->b 라면 compose는 b ->a 이다.

 

 

이는 파이프라인 프로그램 처리를 위해 유용하다고하는데....

뭐 병렬처리에 대한 파이프라인 적 프로그램 로직짤때 유용할듯 싶다.

 

 

3. Function

위에서 말한 compose()는 이 인터페이스에서 나오는 메소드이다.

andthen이 a-> b 라면, compose는 b->a 이다.

T타입으로 인자를 받고, R타입으로 리턴을 해준다.

Function은 compose,andThen이 디폴트고, apply라는 메소드를 사용한다.

 

 

 

4. Predicate (그냥 신기해서 글자수 키움 ㅋ)

T타입을 인자로 받고, boolean 형태를 리턴한다.

이게 뭐하는것이냐?

 

내부적으로 test 메서드를 구현하고, 해당 test메서드에 대한 리턴값으로, 정해진 기준에 대한 결과를 받아볼수 있다.

이렇게 되는 경우, 여러개의 조건을 and or 로 붙일수있다.

즉, Predicate는, 해당 값에 대한 조건 충족 여부를 리턴하는 것이다.

 

개인적인 생각으로는, 기본형을 쓸때는 오토박싱이나 오토 언박싱에 유의,

 

 

 

정리

 

Runnable, run(void)
-> 쓰레드랑 엮어서 사용할때

 

Function,apply(T타입을 받고, 다른 형태의 R타입을 리턴)

-> andThen,compose와 활용 여러개를 apply가능

 

Predicate, test(T타입을 받고 boolean 리턴)

-> and,or과 함께 값 검증으로 활용

 

Consumer,accept(T타입 받고 void 리턴)

-> T타입을 받고, void return, 단순 문자열 붙이기나 리턴값이 필요하지 않고, 기존 데이터에 추가적인 가공 작업을 할때 사용하면 좋다.

 

Supplier ,get(인자 없고, T타입 반환)

-> 지연 계산할때 유용, generate와 random함수를 쓰면 값을 계속준다.함수자체를 매게 변수로 넘겨 지연계산 가능

반응형