본문 바로가기
java

[Java] java8 - Functional Programming이 뭘까 왜 쓰지 그리고 람다식은?

by jinbro 2017. 11. 2.

[github 코드]
- 함수형인터페이스 직접 만들어본 코드는 아래에 나머지 샘플 코드들은 깃헙에 푸쉬해놓음
https://github.com/imjinbro/javaBasic/tree/master/src/com/jinbro/source/fp


[FP OOP에서 ?]

- FP 필요한 부분이 있으니깐 사용하겠다


1) 동시성 side effect 없애기 : 멀티쓰레딩 공유자원 안전

- 객체 상태 변화에 민감한 부분에서 순수함수형프로그래밍이 좋다 : 같은 input -> 같은 output

- 변경 개념이 아니라 복사되고 복사된 것이 함수를 거쳐 결과값으로 : side effect 없앰


2) 함수에만 신경쓰면 : 메서드에만 집중가능함

- 객체지향설계 메서드 single responsibility principle 비슷함, side effect 생각해야하는가 차이가

- 쪼개서 생각하기

- 간결해짐 : 퍼즐 맞추기 같은 느낌이랄까



[Functional Programming]

- 함수로 프로그래밍 하는


1) 함수

- 명령들의 집합이 아니고 X

- 수학의 함수 : input -> output

- 표현식(expression) : input -> output, 반드시 결과값이 있음


2) 함수를 만들어내는 방법 하나인 람다식

- 익명함수 표현식 : 이름없지만 동작하는 함수를 만들어냄

- 자바에서는 1개의 abstract 메서드를 가지는 인터페이스 익명클래스 구현하는

- 표현이 간결해짐 : 추상화된 표현이 가능함, 람다식을 사용하면 인터페이스 타입의 메서드를 구현하는구나 컴파일러가 추론

- 파라미터 개수(제네릭을 사용한다는 가정하에) 맞으면 다양한 함수를 만들어 있음



[자바에서 FP 어떻게하나 - 제공되는 API 함수형 인터페이스를 사용하면서 알아보기]

- 패턴은 이렇다

1) 1 abstract 메서드를 가진 interface 선언함

2) 특정 객체의 메서드의 파라미터 타입이 위에서 만든 인터페이스 타입

3) 특정 객체의 메서드를 호출할 원래라면 인터페이스를 구현하는 new 인터페이스 코드를 작성해야하지만

   람다식을 사용하면 컴파일러가 알아서 인터페이스를 구현하는구나 추론하고 1개의 abstract 메서드와 바인딩함


0) 특정 객체의 메서드 내부적으로는 인터페이스.abstract 선언된 메서드() 하는 것임



[대표적인 함수형 인터페이스를 사용하면서 위의 패턴 익혀보기] : 설명이랑 활용은 위 깃헙

- java.util.function

- @FunctionalInterface : 1개의 abstract 메서드만을 갖도록 강제함


1) Function<T,P> : <T> -> <P>

2) Consumer<T> : <T> -> Void

3) Predicate<T> : <T> -> Boolean

4) Supplier<T> : lazy evaluation



[직접 함수형인터페이스를 만들어서 패턴 익혀보기 : 람다식의 장점을 느껴보자]

1) abstract method 1개만 가지는 인터페이스 : 객체로 생성되지만 1개의 함수처럼

2) @FunctionalInterface 어노테이션을 붙여놓으면 abstract 메서드가 1개인지 컴파일 타임 체크

- default, static 메서드는 체크X : java8부터 추가가능


3) 람다식을 사용할 있음 : 익명메서드, 추론이 가능하기때문에(그자리가 어떤 객체 타입의 메서드인지)

- 내부적으로는 익명클래스구현과 같다고함


4) 람다식을 사용하면 그떄마다 필요한 함수를 갈아끼우는 효과

- 파라미터 개수만 맞다면 : 타입파라미터를 사용하면 타입은 생각안해도되니깐

import java.math.BigDecimal;
import java.util.function.Supplier;

@FunctionalInterface
public interface MFuncInterface<T, R> {
R apply(T data);
//R print(T data); : 2개의 abstract 메서드가 있으면 람다식을 사용할 수 없음 : 어떤 메서드인지 구분X
}

@FunctionalInterface
interface BigDecimalToCurrency{
/* NONE-Generic : 명확한 경우에는 <T> 타입파라미터를 받지않고 명시적으로 지정해놓음 : 람다식을 생성하는 쪽에서 쓰면 추론 */
String toCurrency(BigDecimal value);
}


class MFuncInterfaceUse {

public static void main(String[] args) {
/*
method("Jinhyung", new MFuncInterface<String, String>() {
@Override
public String apply(String data) {
return "Hello! " + data;
}
});
*/

//개수만 같지 전혀 다른 메서드
method("Jinbro", s -> "Hello! " + s);
method("박진형", s -> s + "님 저희 지점을 방문해주셔서 감사합니다");
method(3, s -> s + " 2 1 0 땡!");
method(3, s -> s + "^2 = " + s*s);


hello(true, () -> "Jinbro");
hello(false, () -> "Jinbro");


BigDecimalToCurrency bigDecimalToCurrency = bd -> "$" + bd.toString();
System.out.println(bigDecimalToCurrency.toCurrency(new BigDecimal(120.00)));

}

static <T> void method(T data, MFuncInterface<T, String> func){
//T는 타입파라미터를 받고(추론), R은 String 고정, 내부적으로 func의 apply 메서드 사용
System.out.println(func.apply(data));
}





//내부적으로는 어떻게 사용되나 테스트해봄 : Supplier의 get()
static void hello(boolean isMember, Supplier<String> getName){
if(isMember){
System.out.println(getName.get() + "님 환영합니다");
} else {
System.out.println("회원가입이나 로그인을 해야합니다");
}
}
}



[제약사항]

1) 람다식을 쓴다면 최소한 인터페이스 타입 객체가 생성될 타입파라미터가 있어야함

- 아무런 정보없이 람다식을 사용하면 타입추론이 어려워서 컴파일 단계에서 에러남

- 함수형 인터페이스의 메서드가 제네릭 메서드인 경우 : 호출될 비로소 타입을.....
- 클래스 타입파라미터 사용하기

@FunctionalInterface
public interface InvaildFuncInterface {
<T> String print(T value);
}

class InvalidFuncInterfaceUse{
public static void main(String[] args) {

/* 호출할 때 value가 비로소 어떤 타입인지 알 수 있음 : 추론 불가 */
//getPrint(1, s -> s.toString());
}

public static <T> void getPrint(T value, InvaildFuncInterface invalidFuncInterfaceUse){
System.out.println(invalidFuncInterfaceUse.print(value));
}
}



[함수형인터페이스 - 람다식 이럴 쓴다, 이렇게 쓴다] : 코드는 위 깃헙에...

1) 람다식을 사용할 있는 여지를 만들어준다 : 함수형인터페이스 메서드 동작까지 구현

- 예를 들어 Function<T, R> 이라면 function.apply(T t) 해놓으면 호출부에서 람다식으로 구현

- 실제 함수몸통은 람다식, 이럴 호출된다는 것은 인터페이스 타입 객체가 구현된 곳에



2) 4가지 API 함수형 인터페이스 적절하게 사용

- Function<T, R> : 작업으로 타입 변환할

- Consumer<T> : 작업은 하되 딱히 리턴되는 것이 없을

- Predicate<T> : 작업하면서 true, false 작업이 필요할

- Supplier<T> : 작업을 지연시켜야할 혹은 특정 시점에만 작업될 있도록



[추가적인 내용]

1) Predicate<? super T>

- T ? super 타입이면

- 작업에서 T 가진 메서드를 사용하면




댓글