티스토리 뷰
자바 8에는 다양한 기술들을 살펴보려고 하는데, 그 첫 번째는 함수형 인터페이스다.
함수형 인터페이스에 대한 설명
- 예전부터 사용이 되던 인터페이스
- 추상 메서드가 딱 하나만 있으면 함수형 인터페이스가 된다.
- Single Abstract Method(SAM) 인터페이스
- abstarct는 생략 가능
인터페이스임에도 불구하고 인터페이스 내부에 static 메서드, default 메서드를 다룰 수 있다.
static 메서드나 default 메서드가 있더라도 이 외의 추상 메서드가 단 하나라면 함수형 인터페이스가 된다.
자바가 제공해주는 @FunctionalInterface 애너테이션을 통해 인터페이스를 보다 견고하게 관리할 수 있다.
@FunctionalInterface
public interface DoSomething {
void print();
}
이 애너테이션을 사용하는 이유는 @Override를 사용하는 이유와 비슷하다.
프로그래머의 의도를 명시하는 것으로, 크게 세 가지 목적이 있다.
- 해당 클래스의 코드나 설명 문서를 읽을 이에게 그 인터페이스가 람다용으로 설계된 것임을 알려준다.
- 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일되게 해준다.
- 그 결과 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 막아준다.
그러니 직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 애너테이션을 사용하자.
함수형 인터페이스 사용 방법 [1]. 익명 내부 클래스를 활용
public class UseDoSomething {
public static void main(String[] args) {
DoSomething doSomething = new DoSomething() {
@Override
public void print() {
}
}
}
}
함수형 인터페이스 사용 방법 [2]. 람다를 활용
public class UseDoSomething {
public static void main(String[] args) {
DoSomething doSomething = () ->
System.out.println("eric test");
doSomething.print();
}
}
// 출력결과
test
한 줄일 경우에만 대괄호까지 생략 가능하다.
위 같은 형태(람다)를 메서드 파라미터로 전달하거나 자체를 리턴도 가능하다.
순수 함수
자바에서 함수형 프로그래밍을 구현하려면 순수 함수여야 한다.
순수 함수란 동일한 값을 넣었을 때 결과가 동일해야 한다.
이것이 보장이 안되면 함수형 프로그래밍이라 보기 어렵다.
보장이 안되는 상황 [1] 함수 안에서 바깥에 있는 값을 사용하는 경우
아래 예제에서는 outValue를 사용했기 때문에 보장이 안 된다고 볼 수 있다.
public class App {
public static void main(String[] args) {
int outValue = 10;
DoSomething doSomething = (int value) -> {
System.out.println(value + outValue);
};
doSomething.print(10);
}
}
보장이 안되는 상황 [2] 메서드 외부의 값을 변경하려는 경우
public class App {
public static void main(String[] args) {
DoSomething doSomething = new DoSomething() {
int outValue = 10;
@Override
public void print(int value) {
outValue++;
}
};
doSomething.print(10);
}
}
이렇게 DoSomething 인터페이스 내에 존재하면 문법적으로 가능하지만 다음 코드는 컴파일 에러가 뜬다.
public class App {
public static void main(String[] args) {
int outValue = 10;
DoSomething doSomething = new DoSomething() {
@Override
public void print(int value) {
outValue++;
}
};
doSomething.print(10);
}
}
위 사진과 같이 final 또는 effective final만 사용이 가능하다고 컴파일 에러 메시지가 뜬다.
🐻
하지만 문법이 허용하므로 ‘무조건 하지마’는 아니고 필요에 따라 사용하면 될 것 같다.
함수형 인터페이스 종류
위에서 정의한 DoSomething 인터페이스는 사실 abstract 메서드인 print를 정의할 필요가 없다.
왜냐하면 function 패키지에서 제공하는 Function 함수 인터페이스를 사용하면 된다.
[1]. Interface Function<T,R>
⇒ T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스
- R apply(T t)
이제 DoSomething 인터페이스를 수정해보자.
import java.util.function.Function;
@FunctionalInterface
public class DoSomething implements Function<Integer, Integer> {
@Override
public Integer apply(Integer integer) {
return integer + 10;
}
}
혹은 DoSomething이라는 인터페이스를 따로 구현하지 않고 사용하는 방법도 있다.
import java.util.function.Function;
public class App {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = (i) -> i + 10;
System.out.println(plus10.apply(10));
}
}
Function<T, R> 함수형 인터페이스에는 다음과 같은 함수 조합용 default 메서드가 존재한다.
[1.1]. andThen
import java.util.function.Function;
public class App {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = (i) -> i+10;
Function<Integer, Integer> multiply2 = (i) -> i*2;
System.out.println(plus10.andThen(multiply2).apply(10));
}
}
- plus10을 먼저 한 뒤, multiply2를 실행한다.
[1.2]. compose
import java.util.function.Function;
public class App {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = (i) -> i+10;
Function<Integer, Integer> multiply2 = (i) -> i*2;
System.out.println(plus10.compose(multiply2).apply(10));
}
}
- plus10을 하기 전에, multiply2를 실행한다.
ex) Arrays::asList
[2]. Interface BiFunction<T,U,R>
⇒ Function이 하나의 값(T 타입)을 받는 함수형 인터페이스라면 이건 두 개의 값(T, U 타입)을 받는다.
- R apply(T t, U t)
[3]. Interface Consumer<T>
⇒ T 타입을 받아서 아무것도 리턴하지 않는 함수 인터페이스
- void accept(T t)
함수 조합용 메서드로 andThen이 존재한다.
ex) System.out::println
public class App {
public static void main(String[] args) {
Consumer<Integer> print = (i) -> System.out.println(i);
print.accept(10);
}
}
[4]. Interface Supplier<T>
⇒ T 타입의 값을 제공해주는 함수 인터페이스
- T get()
ex) Instant::now
public class App {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> 10;
Integer s = supplier.get();
System.out.println(s);
}
}
[5]. Interface Predicate<T>
⇒ T 타입을 받아서 boolean을 리턴하는 함수 인터페이스
- boolean test(T t)
함수 조합용 메서드로 and, or, negate가 존재한다.
ex) Collection::isEmpty
[6]. Interface UnaryOperater<T>
⇒ Function<T, R>의 특수한 형태로, T와 R의 타입이 같을 때 사용하는 함수형 인터페이스
ex) String::toLowerCase
[7]. Interface BinaryOperator<T>
⇒ BiFunction<T,U,R>의 특수한 형태로, T, U, R이 모두 같은 타입일 때 사용하는 함수형 인터페이스
ex) BigInteger::add
effective java 아이템 44에서는 표준 함수형 인터페이스를 사용하라고 권고한다.
필요한 용도에 맞는게 있다면 위에서 소개한 7가지의 함수형 인터페이스를 사용하여 다른 코드와의 상호운용성도 높이자.
함수형 인터페이스를 API에서 사용할 때의 주의할 점
서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해서는 안 된다.
클라이언트에게 불필요한 모호함만 안겨줄 뿐이며, 이 모호함으로 인해 실제로 문제가 일어나기도 한다.
ExecutorService의 submit 메서드는 Callable<T>를 받는 것과 Runnable을 받는 것을 다중 정의했다.
그래서 올바른 메서드를 알려주기 위해 형변환해야 할 때가 종종 생긴다.
이런 문제를 피하는 가장 쉬운 방법은 서로 다른 함수형 인터페이스를 같은 위치의 인수로 사용하는 다중정의를 피하는 것이다.
이는 "다중정의는 주의해서 사용하라"는 effective java 아이템 52 조언의 특수한 예라 할 수 있다.
'JAVA > JAVA8' 카테고리의 다른 글
자바 8에 추가된 기술 [6] Optional (0) | 2022.02.05 |
---|---|
자바 8에 추가된 기술 [5] Stream (0) | 2022.01.23 |
자바 8에 추가된 기술 [4] deafult 메서드와 static 메서드 (0) | 2022.01.22 |
자바 8에 추가된 기술 [3] 메서드 레퍼런스 (0) | 2022.01.22 |
자바 8에 추가된 기술 [2] 람다 표현식 (0) | 2022.01.21 |
- Total
- Today
- Yesterday
- Spring
- 알고리즘
- Spring Boot
- 정규표현식
- 클린 코드
- kkoon9
- Kotlin
- BAEKJOON
- 프로그래머스
- JPA
- programmers
- 객체지향
- 이펙티브 자바
- 디자인패턴
- BOJ
- Algorithm
- 클린 아키텍처
- 디자인 패턴
- Java
- MSA
- 코테
- AWS
- kotest
- 테라폼
- node.js
- 백준
- Effective Java
- C++
- 이팩티브 자바
- Olympiad
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |