기록하자..
람다식 | 백기선님 LIVE-STUDY 본문
람다식
학습할 것
- 람다식 사용법
- 함수형 인터페이스
- Variable Capture
- 메소드, 생성자 레퍼런스
람다식이란?
자바는 함수적 프로그래밍을 위해 자바 8부터 람다식을 지원하기 시작했다.
람다식은 익명 함수를 생성하기 위한 식으로 객체 지향 언어보다는 함수 지향 언어에 가깝다.
람다식을 통해 컬렉션의 요소를 필터링하거나 매핑하게 되면서 자바 코드는 훨씬 간결해지게 되었다. 람다식은 매개 변수를 가진 코드 블럭 형태이지만, 런타임 시에는 익명 구현 객체를 생성한다.
람다식은 (매개변수) -> {실행코드} 형태로 작성이 된다.
Runnable runnable = () -> { ... };
마치 함수 정의 형태를 띄고 있지만 런타임 시에 인터페이스의 익명 구현 객체로 생성된다.
람다식 사용법
함수적 스타일의 람다식을 작성하는 방법은 다음과 같다.
(타입 매개변수) -> { 실행문; }int getDouble(int a) {
return a * 2;
}
(int a) -> { return a * 2; }
여기서 매개 변수 타입은 런타임 시에 대입되는 값에 따라서 타입 추론이 되기 때문에 람다식에서는 일반적으로 타입을 언급하지 않는다.
(a) -> { return a * 2; }
하나의 매개 변수만 있다면 () 를 생략할 수 있고, 마찬가지로 하나의 실행문만 있다면 {} 를 생략할 수 있다.
a -> return a * 2
중괄호 {} 에 return 문만 있는 경우 람다식에서는 일반적으로 return 을 사용하지 않는다.
a -> a * 2
함수형 인터페이스
자바는 메서드를 단독으로 선언할 수 없고 항상 클래스의 구성멤버로 선언하기 때문에 람다식은 메서드를 가지고 객체를 생성한다.
그렇다면 람다식은 어떤 타입의 객체를 생성하는 것일까?
인터페이스 변수 = 람다식;람다식은 인터페이스 변수에 대입된다. 이는 람다식이 인터페이스의 익명 구현 객체를 생성한다는 말이다.
인터페이스는 직접 객체화할 수 없기 때문에 람다식이 익명 구현 클래스를 생성하고 객체화한다. 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타겟 타입이라고 한다.
그런데 모든 인터페이스를 람다식의 타겟 타입으로 사용할 수 없다. 람다식이 하나의 메서드만 정의하기 때문에 하나의 추상 메서드가 선언된 인터페이스만이 람다식의 타겟 타입이 될 수 있다. 이런 인터페이스를 함수형 인터페이스라고 한다.
함수형 인터페이스를 선언할 때 컴파일러가 애노테이션을 통해 두 개 이상의 메서드가 선언되었는지 확인할 수 있는 기능을 제공해주는데, @FunctionalInterface 이다.
@FunctionalInterface
public interface MyFunctionalInterface {
void method();
}
위와 같은 인터페이스를 타겟 타입으로 가지는 람다식은 다음과 같이 작성한다.
MyFunctionalInterface fi = () -> {
String str = "method call";
System.out.println(str);
};
자바 8부터 기본적인 함수형 인터페이스는 java.util.function 표준 API 패키지로 제공해준다.
Consumer<T>
Consumer<T>는 리턴값이 없는 accept() 메서드를 가지고 있다. accept() 메서드는 단지 매개값을 소비한다.
Consumer<String> consumer = t -> System.out.println(t);
consumer.accept("Java");
JavaSupplier<T>
Supplier<T>는 인자를 받지 않고 T 타입의 객체를 리턴한다.
Supplier<String> supplier = () -> "Supplier";
String getSup = supplier.get();
System.out.println(getSup);
SupplierFunction<T, R>
Function 함수형 인터페이스는 매개값과 리턴값이 있는 applyXXX() 메서드를 가지고 있다. 이 메서드는 매개값을 리턴값으로 매팡하는 역할을 한다.
Student student = new Student("Student name");
Function<Student, String> function = t -> t.getName();
String name = function.apply(student);
System.out.println(name);
Student namePredicate<T>
Predicate 함수형 인터페이스는 매개 변수와 boolean 리턴값이 있는 testXXX() 메서드를 가지고 있다. 이 메서드는 매개값을 조사해서 boolean 값인 true 혹은 false를 리턴한다.
Student student = new Student("Man");
Predicate<Student> predicate = t -> t.getSex().equals("Man");
boolean isMan = predicate.test(student);
System.out.println(isMan);
trueVariable Capture
람다식의 실행 블럭에서 클래스의 멤버 및 로컬 변수를 사용할 수 있다. 클래스의 멤버는 제약사항이 없지만 로컬 변수에는 제약사항이 있다.
클래스의 멤버 사용
위에서 언급했듯이 람다식의 실행 블럭에는 클래스의 멤버 및 로컬 변수를 사용할 수 있다. 그런데 this 키워드를 이용할 때 주의가 필요하다.
람다식 내부에서 this 키워드는 내부적으로 생성되는 익명 객체의 참조가 아니라, 람다식을 실행하는 객체의 참조이다.
로컬 변수의 사용
람다식은 메서드 내부에서 주로 작성되게 되기 때문에 로컬 익명 구현 객체를 실행시킨다고 봐야 한다.
람다식 내부에서 메서드의 매개 변수 또는 로컬 변수를 사용하면 이 두 변수는 final 특성을 가져야 한다.
이유는 메서드 내에서 생성한 익명 객체는 메서드 실행이 끝나도 힙 영역에 존재해서 계속해서 사용할 수 있는 반면에, 매개 변수나 로컬 변수는 메서드의 실행이 끝나면 스택 메모리에서 사라지기 때문에 그렇다.
더 자세하게 말해보자면, 람다는 힙 메모리에서 생성되고 인스턴스 변수들는 쓰레드끼리 공유가 가능하지만 로컬 변수는 스택 메모리에 생성되고 이는 별도의 쓰레드마다 생성이 되고 쓰레드끼리 공유가 되지 않는다.
여기서 메서드가 끝나서 스택 메모리는 사라졌음에도 불구하고 람다는 힙 메모리에 살아있을 가능성이 있다.
여기서 스택 메모리의 로컬 변수를 참조하고 있다면 오류가 날 것이다. 그런데 오류가 나지 않는 이유는 이 로컬 변수를 자신의 쓰레드에 복사하기 때문이다. 이를 Variable Capture라고 한다.
메서드, 생성자 레퍼런스
메서드 레퍼런스
메서드 참조는 메서드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어 람식에서 불필요한 매개변수를 제거하는데 목적이 있다.
interface MethodReferenceInterface {
void multiply(int num);
}
public class MyClass {
public static void main(String[] args) {
MethodReferenceInterface mri =
MyClass::multiplyNum;
mri.multiply(10);
}
public static void multiplyNum(int num) {
System.out.println(num * 3);
}
}
30생성자 레퍼런스
메서드 참조는 생성자 참조도 포함한다. 생성자를 참조한다는 것은 객체 생성을 의미한다.
(a, b) -> { return new SomeClass(a, b); }
SomeClass::new
참고 자료
'자바' 카테고리의 다른 글
| Java에서 String은 왜 Immutable 한가 (0) | 2022.10.10 |
|---|---|
| 제네릭 | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
| I/O | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
| Annotation | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
| Enum | 백기선님 LIVE-STUDY (0) | 2022.08.06 |