✅ 람다 특징
람다 는 익명함수를 지칭하는 일반적인 용어 = 이름 없이 함수 표현람다식 은 (매개변수) -> {본문} 형태로 람다를 구현하는 구체적인 문법 표현을 지칭람다 를 사용할 때 new 키워드를 사용하지 않지만, 인스턴스가 생성된다람다 를 사용하면 표현이 간결하다람다 는 변수처럼 다룰 수 있다익명 클래스 는 람다 로 대체할 수 있다람다 가 익명 클래스 를 완전히 대체할 수 있는 것은 아님반환타입 메서드명(매개변수) { // 일반 함수 표현식
본문
}
public int add(int x){
return x + 1;
}
(매개변수) -> {본문} // 람다 표현식
(int x) -> {return x + 1;}
람다 사용방법 : 함수명, 반환 타입은 생략하고 매개변수와 본문만 간단하게 적으면 됨
✅ 함수형 인터페이스
람다 는 추상 메서드가 하나인 인터페이스에만 할당할 수 있음SAM (Single Abstract Method) 라고 부름public interface NotSamInterface {
void run(); // 메서드 앞에는 abstract 키워드가 생략되있음
void go();
// 여기서는 두개의 추상 메서드가 선언되 있으므로, 단일 추상메소드가 아니다
// 그래서 이 인터페이스에는 람다를 할당할 수 없다
}
왜 위의 코드에서 람다 를 할당할 수 없을까?
-> 람다는 하나의 함수라서, 람다를 인터페이스에 담으려면 하나의 메서드 선언만 존재해야함
-> 두개 이상이면 어떤 메서드에 할당해야하는지 알 수 없기 때문에 컴파일 오류 발생
인터페이스에 하나의 추상 메서드만 사용한다라고 표현하려면 @FunctionalInterface 를 사용
@FunctionalInterface : 함수형 인터페이스 임을 선언
이 어노테이션을 사용하면 누군가 실수로 추상 메서드를 추가하면 컴파일 오류가 발생
따라서 람다를 사용할 함수형 인터페이스 라면 @FunctionalInterface 필수로 추가 권장
✅ 람다와 생략
예제 (1)
@FunctionalInterface
public interface MyFunction { // MyFunction 인터페이스
int apply(int a, int b);
}
위 MyFunction 인터페이스를 재정의하면 다음과 같이 표현할 수 있다
MyFunction function1 = (int a, int b) -> {
return a + b;
};
위 function1 처럼 단일표현식 인 경우 중괄호와 리턴을 생략할 수 있다
MyFunction myFunction2 = (int a, int b) -> a + b;
하지만 여러문장이 있으면 (단일표현식) 이 아닌 경우는 생략하면 안된다
MyFunction myFunction3 = (int a, int b) ->{
System.out.println("hi");
return a + b;
};
함수형 인터페이스에 이미 int 형으로 정의되어 있으므로, 매개변수 타입을 생략할 수 있다
MyFunction myFunction4 = (a,b) -> a + b;
만약에 매개변수가 딱 하나라면 () 를 생략할 수 있다 (매개변수 없거나, 두개 이상이면 안됨)
MyFunction myFunction5 = a -> a * 2;
다음은 Procedure 인터페이스의 예시를 보여주겠다
public interface Procedure {
void run();
}
위 문장을 재정의하면 다음과 같이 표현할 수 있다
Procedure procedure = () ->{
System.out.println("hi");
};
하지만 위 문장은 단일표현식 이므로 중괄호를 생략할 수 있다
Procedure procedure2 = () -> System.out.println("hi");
✅ 람다의 전달
람다도 인터페이스를 사용하므로, 람다 인스턴스 참조값을 변수에 전달할 수 있다
MyFunction add = (a,b) -> a+b;
MyFunction sub = (a,b) -> a-b;
System.out.println(add.apply(2,3)); // 5
System.out.println(sub.apply(2,3)); // -1
// 변수를 통해 전달
MyFunction cal = add; // 람다를 변수에 대입
System.out.println(cal.apply(2,3)); // 5
// 람다를 직접 전달
calculate((a,b) -> a + b);
calculate((a,b) -> a - b);
static void calculate(MyFunction function) {
int a = 1;
int b = 2;
int result = function.apply(a, b);
System.out.println(result);
}
또 람다를 반환할 수 도 있다
public static void main(String[] args) {
MyFunction add = getOperation("add");
System.out.println(add.apply(1, 2)); // 3
MyFunction sub = getOperation("sub");
System.out.println(sub.apply(1, 2)); // -1
MyFunction xxx = getOperation("xxx");
System.out.println(xxx.apply(1, 2)); // 0
}
// 람다를 반환하는 메서드
static MyFunction getOperation(String operator) {
switch (operator) {
case "add":
return (a, b) -> a + b;
case "sub":
return (a, b) -> a - b;
default:
return (a, b) -> 0;
}
}
✅ 고차함수
다음 2가지 중 하나를 만족하면 고차 함수 라고 한다
static void calculate(MyFunction function) { // 함수(람다)를 인자로 받는 경우
// ...
}
static MyFunction getOperation(String operator) { // 함수(람다)를 반환하는 경우
// ...
return (a, b) -> a + b;
}
보통의 일반 함수들은 인수로 값을 받고, 값을 반환한다
하지만 고차함수는 함수로 인자를 받거나 함수를 반환한다
고차함수는 함수라는 개념 자체를 값처럼 다룬다는 점에서 추상화 의 수준(계층)이
한 단계 높아진다고 해서 고차함수 라고 한다
✅ 함수형 인터페이스에 제네릭 도입
@FunctionalInterface
interface GenericFunction<T, R> {
R apply(T s); // T : 매개변수 타입 R : 반환 타입
}
GenericFunction<String, String> toUpperCase = str -> str.toUpperCase();
GenericFunction<String, Integer> stringLength = str -> str.length();
GenericFunction<Integer, Integer> square = x -> x * x;
GenericFunction<Integer, Boolean> isEven = num -> num % 2 == 0;
System.out.println(toUpperCase.apply("hello")); // HELLO
System.out.println(stringLength.apply("hello")); // 5
System.out.println(square.apply(3)); // 9
System.out.println(isEven.apply(3)) // false
제네릭을 사용하면 동일한 구조의 함수형 인터페이스를 다양한 타입에 재사용할 수 있다
제레닉을 넣음으로써, apply() 의 매개변수와 반환 타입을 유연할게 할 수 있다
위에서 선언한 GenericFunction() 은 매개변수가 1개고, 반환값이 있는 모든 람다에 사용 가능
💡 하지만 람다르 사용하려면 함수형 인터페이스가 필수이기 때문에,