람다식이란?
메서드를 하나의 식으로 표현한 것.
→ 메서드의 이름과 반환값이 없어짐 = 익명함수
→ 클래스나 객체 생성 없이 메서드의 역할 수행 가능.
→ 메서드의 매개변수 및 결과로 반환 가능 = 메서드를 변수처럼 다루는 것
💡 람다식 작성 방법<기존 함수 선언 방법>
반환타입 메서드명 (변수 선언) {
문장들 }
<람다식>
(매개변수 선언) → {
문장들 }
→ 매개변수의 반환 타입이 추론 가능한 경우 생략
→ 선언된 매개변수가 한개인 경우 괄호 생략
(단, 매개변수 타입이 있으면 괄호 생략 불가)
→ {중괄호} 안의 문장이 하나인 경우 생략 가능. (이때 문장의 끝에 ‘;’ 붙이지 않음)
(단, {괄호}안의 문장이 return 문일 경우 생략 불가)
int max(int a, int b){
return a>b ? a:b;}
(int a, int b) -> {return a>b ? a:b;}
(int a, int b) -> a>b? a:b
(a,b) -> a>b? a:b
람다식을 다루기 위한 인터페이스를 함수형 인터페이스라고 함.
@FunctionalInterface
interface Myfunction{
public abstract int max(int a, int b);
}
→ 오직 하나의 추상 메서드만 정의되어야 함 (그래야 람다식과 인터페이스가의 메서드가 1:1로 연결)
→ 반면, static과 default 메서드의 개수에는 제약이 없음
@FunctionalInterface
interface MyFunction {
void run(); // public abstract void run();
}
class LambdaEx1 {
static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
f.run();
}
static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드
MyFunction f = () -> System.out.println("f3.run()");
return f;
}
public static void main(String[] args) {
// 람다식으로 MyFunction의 run()을 구현
MyFunction f1 = ()-> System.out.println("f1.run()");
MyFunction f2 = new MyFunction() { // 익명클래스로 run()을 구현
public void run() { // public을 반드시 붙여야 함
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
execute(f1);
execute( ()-> System.out.println("run()") );
}
}
-함수형 인터페이스로 람다식을 참조하는 것이지 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아님. (람다식은 익명 객체이고 익명 객체는 컴파일러가 임의로 이름을 정하기 때문에 타입을 알 수 없음.)
→ 대입 연산자의 양변의 타입을 일치시키기 위해 형변환이 필요
→ 람다식은 Object 타입으로 형변환 불가. 오직 함수형 인터페이스로만 형변환 가능.
java.util.function패키지의 주요 함수형 인터페이스
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
Runnable | void run() | 매개변수도 없고, 반환값도 없음 |
Supplier | T get() | 매개변수는 없고, 반환값만 있음 |
Consumer | void accept(T t) | Supplier와 반대로 매개변수만 있고, 반환값이 없음 |
Function<T,R> | R apply(T t) | 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환 |
Predicate | boolean test(T t) | 조건식을 표현하는데 사용됨. 매개변수는 하나, 반환타입은 boolean |
람다식의 실행 블록 내에서 람다식을 감싸고 있는 클래스의 인스턴스 변수, 스태틱 변수, 지역 변수에 접근하는 것이 가능
→ 그러나 지역 변수에 접근할 때는 Variable Capture라는 특별한 작업이 수행되기 때문에 한 가지 제약이 생김
= Variable Capture란 객체 외부에서 선언된 변수를 객체 내부로 복사하는 행위. ****지역 변수 뿐만 아니라 파라미터로 전달된 변수 또한 외부에서 선언된 변수이므로 같은 규칙이 적용
public class CaseOne {
static int staticVariable = 10;
int instanceVariable;
@FunctionalInterface
interface MyFunction {
void myMethod();
}
public static void main(String[] args) {
CaseOne caseOne = new CaseOne();
caseOne.instanceVariable = 20;
int localVariable = 30;
MyFunction myFunction = () -> {
System.out.println("static variable = " + CaseOne.staticVariable);
System.out.println("instance variable = " + caseOne.instanceVariable);
System.out.println("local variable = " + localVariable);
};
myFunction.myMethod();
}
}
실행결과
static variable = 10
instance variable = 20
local variable = 30
스태틱 변수나 인스턴스 변수를 변경하는 것은 문제가 없으나, 지역변수를 변경하는 순간 오류가 발생
⇒ 오류가 발생하는 이유는 클래스 내부에 선언된 로컬 클래스(여기선 람다식)가 지역 변수를 참조할 때는 그 값을 복사해서 사용하기 때문(=Variable Capture)
public class CaseOne {
static int staticVariable = 10;
int instanceVariable;
@FunctionalInterface
interface MyFunction {
void myMethod();
}
public static void main(String[] args) {
CaseOne caseOne = new CaseOne();
caseOne.instanceVariable = 20;
int localVariable = 30;
// 값 변경
CaseOne.staticVariable += 5;
caseOne.instanceVariable += 5;
localVariable += 5;
MyFunction myFunction = () -> {
System.out.println("static variable = " + CaseOne.staticVariable);
System.out.println("instance variable = " + caseOne.instanceVariable);
System.out.println("local variable = " + localVariable); // 컴파일 오류 발생!
};
myFunction.myMethod();
}
}
1) 메서드 참조
람다식이 하나의 메서드만 호출하는 경우에는 메서드 참조를 통해 간략히 할 수 있음
Function<String , Integer> f = (String s) -> Integer.parseInt(s);
Function<String , Integer> f = Integer::parseInt;
“클래스이름::메서드이름” 혹은 ”참조변수:메서드이름”으로 바꿀 수 있음
2)생성자의 메서드 참조