티스토리 뷰
목표
자바의 람다식에 대해 학습하기
스터디 목차
- 람다식 사용법
- 함수형 인터페이스
- Variable Capture
- 메소드, 생성자 레퍼런스
1. 람다식 사용법
람다식 이란?
자바 8부터는 객체지향 프로그래밍과 함수적 프로그래밍을 혼합할 수 있게 람다식을 제공한다.
람다식(Lambda expression)은 메서드를 하나의 식으로 표현한 것이다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 익명 함수라고도 한다.
람다식 예제
일반적인 plus 메서드를 람다식으로 바꿔보기
int plus(int x, int y){
return x+y;
}
우선 람다식은 메서드 이름과 반환 타입을 제거할 수 있다.
또한, 반환값이 있는 경우 return 키워드 대신에 식으로 대신할 수 있다. (식의 연산결과가 반환값이 되므로 세미콜론 생략)
그리고 함수 몸체 {} 괄호 안에 한 문장일 경우 괄호 또한 생략할 수 있다.
(int x, int y) -> x + y
그리고 람다식에 선언된 매개변수 타입은 추론이 가능한 경우 생략할 수 있다.
(x, y) -> x + y
2. 함수형 인터페이스 (Functional Interface)
함수형 인터페이스는 단 하나의 추상 메소드만이 선언된 인터페이스 이다.
MyFunction 인터페이스는 다음과 같이 정의되어 사용될 수 있다.
MyFunction f = new MyFunction() {
public int plus(int x, int y) {
return x + y;
}
};
f. plus(1,2);
이때, MyFunction 인터페이스에 정의된 plus 메서드를 다음과 같이 람다식으로 표현할 수 있다.
MyFunction f = (int x, int y) -> x + y;
f.plus(1,2);
MyFunction 인터페이스를 구현한 익명 객체를 람다식으로 표현할 수 있는 이유는 람다식도 사실 익명 객체이고,
인터페이스를 구현한 익명 객체의 메서드 plus와 람다식이 매개변수 타입, 개수, 반환값이 일치하기 때문이다.
반드시 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야한다.
왜냐하면 람다식과 인터페이스의 메서드가 1:1로 연결되어야 하기 때문이다.
3. Variable Capture
람다의 바디에서는 파라미터 말고 바디 외부에 있는 변수를 참조할 수 있다.
public class LambdaCapturing {
private int a = 12;
public void test() {
int b = 123;
final Runnable r = () -> System.out.println(a);
final Runnable r2 = () -> System.out.println(b);
}
}
이렇게 람다 시그니처의 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수를 자유 변수라고 부른다.
또한 람다 바디에서 자유변수를 참조하는 행위를 람다 캡처링(Lambda Capturing)이라고 부른다.
람다 캡처링의 제약조건
- 지역변수는 final로 선언되어 있어야한다.
- final로 선언되지 않은 지역변수는 final처럼 동작해야한다. (즉, 값의 재할당이 일어나면 안된다.)
public class LambdaCapturing {
private int a = 12;
public void test() {
final int b = 123;
int c = 123;
int d = 123;
final Runnable r = () -> {
// 인스턴스 변수 a는 final로 선언돼있을 필요도, final처럼 재할당하면 안된다는 제약조건도 적용되지 않는다.
a = 123;
System.out.println(a);
};
// 지역변수 b는 final로 선언돼있기 때문에 OK
final Runnable r2 = () -> System.out.println(b);
// 지역변수 c는 final로 선언돼있지 않지만 final을 선언한 것과 같이 변수에 값을 재할당하지 않았으므로 OK
final Runnable r3 = () -> System.out.println(c);
// 지역변수 d는 final로 선언돼있지도 않고, 값의 재할당이 일어났으므로 final처럼 동작하지 않기 때문에 X
d = 12;
final Runnable r4 = () -> System.out.println(d);
}
}
왜??
JVM에서 지역변수는 스택에 생성된다. 그리고 실제 메모리 와는 달리 JVM에서 스택 영역은 쓰레드마다 별도의 스택이 생성된다. 따라서 지역변수는 쓰레드끼리 공유가 안 된다.
JVM에서 인스턴스 변수는 힙 영역에 생성된다. 인스턴스 변수는 쓰레드끼리 공유가 가능하다.
람다는 별도의 쓰레드에서 실행이 가능한데 원래 지역 변수가 있는 쓰레드는 사라져서 해당 지역변수가 사라졌는데도 불구하고, 람다가 실행 중인 쓰레드는 살아있을 가능성이 있다.
람다에서는 지역변수(해당 쓰레드의 스택)에 직접적으로 접근하는게 아니라 변수를 자신(쓰레드)의 스택에 복사하기 때문에 해당 지역변수가 사라진다고 에러는 나지는않지만 위와 같이 변수를 복사해서 쓰는데 그 변수의 값이 중구난방으로 변경된다고 하면 해당 복사본을 믿고 쓸 수 없다.
따라서 지역 변수에는 final이어야 하거나 final 같이 동작해야한다는 제약조건이 생긴 것이다.
그렇다면 인스턴스 변수는 왜 이런 조건이 없는걸까?
이는 인스턴스 변수는 힙에 존재하고, 쓰레드끼리 공유도 가능하기 때문에 별도로 복사할 필요도 없고, 직접 힙에 접근해서 사용하면 되기 때문이다.
4. 메소드, 생성자 레퍼런스
메서드 레퍼런스
메서드 레퍼런스는 람다 표현식이 단 하나의 메서드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해준다.
메서드 레퍼런스는 메서드명 앞에 구분자(::)를 붙이는 방식으로 사용된다.
Method Reference를 만드는 유형에는 다음 3가지가 있다.
(1) 정적 메서드 참조 레퍼런스 : Integer의 parseInt 메서드는 Integer::parseInt
(2) 다양한 형식의 인스턴스 메서드 레퍼런스 : String의 length 메서드는 String::length
(3) 기존 객체의 인스턴스 메서드 레퍼런스 : Car 객체를 할당받은 bmw 지역변수가 있고, Car객체에는 getPrice라는 메서드가 있다면 bmw::getPrice라고 표현할 수 있다.
예제를 바탕으로 다시 보면
(1) 정적메서드 레퍼런스
- 파라미터로 전달받은 변수의 메서드를 사용하지 않고, 정적 메서드의 인자로 사용됨
(2) 다양한 형식의 인스턴스 메서드 레퍼런스
- 파라미터로 전달받은 변수의 메서드를 사용함
(3) 기존 객체의 인스턴스 메서드 레퍼런스
- 1번과 비슷한데 차이점이라면 정적 메서드의 인자가 아닌 기존에 이미 생성된 인스턴스의 인자로 사용된다.
생성자 레퍼런스
생성자를 호출하는 람다 표현식도 앞서 살펴본 메서드 참조를 이용할 수 있다.
즉, 단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변환할 수 있다.
다음 예제는 단순히 객체를 생성하고 반환하는 람다 표현식이다.
(a) -> { return new Object(a); }
위의 예제는 단순히 Object 클래스의 인스턴스를 생성하고 반환하기만 하므로, 생성자 참조를 사용하여 다음과 같이 간단히 표현할 수 있다.
Object::new
이때 해당 생성자가 존재하지 않으면 컴파일시 오류가 발생한다.
또한, 배열을 생성할 때에도 다음과 같이 생성자 참조를 사용할 수 있다.
Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new; // 생성자 참조
Reference
[Java] 람다(Lambda)
본 시리즈에서는 Java8에 추가된 람다식과 함수형 프로그래밍에대해 알아봅니다. Java8부터 람다식이 추가되면서 Java는 객체지향언어인 동시에 함수형 언어의 기능까지 갖추게 되었습니다. 함수
inma.tistory.com
perfectacle.github.io/2019/06/30/java-8-lambda-capturing/
(Java) 람다 캡처링과 final 제약조건
람다의 바디에서는 파라미터 말고 바디 외부에 있는 변수를 참조할 수 있다. 1234567891011public class LambdaCapturing { private int a = 12; public void test() { int b = 123; final Runnable r = () -> S
perfectacle.github.io
[Java8] Method Reference (메서드 참조) - 생성 방법
Method Reference를 이해하려면 Lambda를 먼저 이해해야한다. 여기서는 Lambda식을 Method Reference로 변경하는 방법을 다룬다. Lambda식을 사용하다보면 IDE에서 친절하게 노란색으로 Method Reference로 바꾸지..
countryxide.tistory.com
www.tcpschool.com/java/java_lambda_reference
코딩교육 티씨피스쿨
4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등
tcpschool.com
'Live Study' 카테고리의 다른 글
[JAVA 스터디] 14주차 과제 : 제네릭 (0) | 2021.02.24 |
---|---|
[JAVA 스터디] 13주차 과제 : I/O (0) | 2021.02.08 |
[JAVA 스터디] 12주차 과제 : 애노테이션 (0) | 2021.02.01 |
[JAVA 스터디] 11주차 과제 : Enum (0) | 2021.01.25 |
[JAVA 스터디] 10주차 과제 : 멀티쓰레드 프로그래밍 (0) | 2021.01.19 |