Function<T, R> 과 apply()
T : input type R : return type
background
기본적으로 자바는 타입이 ‘기본형’과 ‘객체형’이 있다. 그냥 기본형이 아닌건 다 객체다.
📌 기본형 (Primitive Type)
- 논리 : boolean
- 문자 : char
- 정수 : byte, short, int, long
- 실수 : float, double
함수가 객체라면 다른 객체들처럼 컨테이너에 저장할 수 있어야 하는게 아닐까?
그런데 클래스 안에서 선언하는거 이외에 함수를 객체로써 사용을 하시나요?
C++에서는 함수 객체 개념을 위해 std::function, std::find가 있는데 Java는 어떨까?!
Function (java.util.function)
Function (Java Platform SE 8 )
Function (Java Platform SE 8 )
docs.oracle.com
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
Java 8 에서 부터 제공하는 Function 를 이용하면 함수를 어떤 변수에 할당할 수 있다.
이 포스팅에서는 이런 개념이 있다는 것을 알리는 것이 목적이므로, Interger 매개변수 1개에 Integer 반환 타입1개를 을 다루겠습니다.
💡 function에서 매개변수의 개수와 타입에 따라 여러 종류를 지원하고 있습니다.
- 기본형 매개변수는 따로 XXFunction이 인터페이스가 있음
- IntFunction
- 기본형 매개변수에 기본형 리턴값도 따로 XXtoYYFunction이 인터페이스가 있음
- DoubleToIntFunction
사용 방법
# 선언 방법
Function<매개변수타입, 리턴타입> 변수명
Function<Integer, Integer> fn = (Integer x) -> Operation.op2(x);
이런 식으로 함수나 람다를 저장할 수 있다.
# 호출 방법
funtion에 내장된 apply() 라는 메서드를 이용해 호출한다.
for(Function<Integer, Integer> op : tasks)
{
int e = 1;
x = op.apply(e); // apply에 파라미터를 건낸다.
System.out.println(e);
}
활용 예시
예시 1
람다나 함수를 배열로 저장할 수 있다. 할당할 수 있으니까!
static Function<Integer, Integer>[] tasks = new Function[]{
n -> (Integer)n + 1,
n -> (Integer)n - 1,
n -> (Integer)n * 2
};
예시 2
덕타이핑을 통한 유연한 프로그래밍이 가능해진다.
한 클래스에 여러 연산이 있는 예시를 작성해보았다.
static class Operation
{
static int op1(final int x)
{
return x + 1;
}
static int op2(final int x)
{
return x-1;
}
static int op3(final int x)
{
return x*2;
}
}
만약 위 멤버 함수를 모두 한번씩 호출해야한다고 생각해보자.
한 줄씩 호출해도 되지만 Function을 매개변수로 이용해서 사용 방법을 통일화 할 수 있다.
static int calc(Function<Integer, Integer> op, final int n){
return op.apply(n);
}
>> main
System.out.println("멤버 함수 op1 호출 : " + calc(Operation::op1, 5));
System.out.println("멤버 함수 op2 호출 : " + calc(Operation::op2, 5));
System.out.println("멤버 함수 op3 호출 : " + calc(Operation::op3, 5));
만약 호출자체가 사용자의 입력에 달린 것이라면? 런타임에 어떤 함수가 호출될지 고를 수 있는 동적 프로그래밍이 되는 것이 큰 특징이다.
>> 결과값
멤버 함수 op1 호출 : 6
멤버 함수 op2 호출 : 4
멤버 함수 op3 호출 : 10
어떻게 활용될 수 있나 스스로 궁리하느라 예시가 빈약한 점 죄송하다는 말을 하며..
일단 이쯤하면 어떻게 사용하는지는 알았을 것이다.
진짜 장점은 간단한 람다와 반복문에서 극대화되는 것 같다!!
간단한 연산을 여러번 반복해야한다면 Funtion을 컨테이너에 저장한 뒤 활용하면 좀 더 코드를 깔끔하게 만들 수 있다.
static Function<Integer, Integer>[] tasks = new Function[]{
x -> Operation.op1((Integer) x),
x -> Operation.op2((Integer) x),
x -> Operation.op3((Integer) x)
};
이러면 똑같은 apply라는 함수로 여러 연산을 계산할 수 있다.
for(Function<Integer, Integer> op : tasks) {
System.out.println("반복문 테스트 : " + op.apply(5));
}
반복문 테스트 : 6
반복문 테스트 : 4
반복문 테스트 : 10
마치며..
Function 클래스에는 아래 그림처럼 함수 실행 전에 뭘 해야한다고 조건을 주는 compose, 실행 후 조건을 주는 andThen() 이라는 것도 있다. 하지만 아직 쓸 일이 없었으므로 다음에 포스팅해보겠습니다!
첨언하자면 숨바꼭질2라는 문제를 풀다가 연산 if문으로 분기 하기 싫어서 여기까지 와버렸습니다..
이렇게 if문 범벅인 코드를
for(int i=0; i<3; ++i)
{
if(i==0) x = e + 1;
else if(i ==1) x = e - 1;
else if(i==2) x = e *2;
if(x == k) ++cnt;
if(x < 0 || x >= MAX_SIZE || visited[x]) continue;
q.offer(x);
}
짜잔 간단하게 바꿀 수 있습니다.
for(Function<Integer, Integer> op : tasks)
{
x = op.apply(e);
if(x == k) ++cnt;
if(x < 0 || x >= MAX_SIZE || visited[x]) continue;// || ((visited & (1<<x))!=0)) continue;
q.offer(x);
}
만약에 연산의 수가 계속 변동된다면 for문의 반복 횟수도 고쳐야 하며, 내부 if문을 추가해야 하지만
Function을 쓰면 배열에 추가할 함수만 더 추가해주면 그만이니 정말 편리한 기능입니다.
Function보다 if문이 속도는 더 빠르다는 사실을 알리며 총총…