-
Java - 람다식Java/java study 2022. 4. 10. 21:28728x90반응형
- 람다식 사용법
- 함수형 인터페이스
- Variable Capture
- 메서드, 생성자 레퍼런스
람다식이란?
람다식은 JDK 1.8부터 추가된 것으로 메서드를 하나의 '식(expression)'으로 표현한 것이다. 람다식을 사용함으로써 코드가 간결해지고 또한 가독성이 증가되는 여러 이점등이 있다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 익명함수(anonymous function)라고도 한다.
람다식의 특징
- 익명
- 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다.
- 구현해야 할 코드에 대한 걱정거리가 줄어듬 - 함수
- 람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 불린다.
- 메서드처럼 파라미터 리스트, 바디, 반환형식, 가능한 예외 리스트를 포함한다. - 전달
- 람다 표현식을 메서드 인수로 전달하거나 변수로 저장 할 수 있다. - 간결성
- 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없다.
//람다식을 적용하기 전 코드 Comparator<String> s = new Comparator<String>() { public int compare(String s1, String s2) { return s1.compareTo(s2); } } //람다식을 적용 Comparator<String> s = (String s1, String s2) -> s1.compareTo(s2));
람다식 사용법
// 메서드 반환타입 메서드이름(매개변수 선언){ 문장들 } // ex int max(int a, int b){ return a> b ? a: b; } // 람다식 (매개변수 선언) -> { 문장들 } // ex (int a, int b) -> { return a > b ? a: b; }
람다식 표현 방법
1. 반환값이 있는 메서드의 경우, return문 대식 '식(expression)'으로 대신 할 수 있다. 식의 연산결과가 자동적으로 반환 값이 되며 '문장(statement)'이 아닌 식이므로 끝에 세미콜론(;)을 붙이지 않는다.
(int a, int b) -> { return a > b ? a : b; } // return문 (int a, int b) -> a > b ? a : b // 식(expression)
2. 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우에 생략이 가능하다. 대부분의 경우 생략이 가능하며 람다식에 람다식에 반환타입이 없는 이유도 항상 추론이 가능하기 때문이다. 단, 두 매개변수 중 어느 하나의 타입만 생략하는 것은 허용되지 않는다.
(int a, int b) -> a > b ? a : b (a, b) -> a > b ? a : b // 매개변수의 타입 생략
3. 선언된 매개변수가 하나뿐인 경우에는 괄호()를 생략할 수 있다. 단, 매개변수의 타입이 있으면 생략할 수 없다.
a -> a * a // Ok. int a -> a * a // error. (int a) -> a * a // Ok.
4. 괄호{} 안의 문장이 하나일 때는 역시 괄호{}를 생략할 수 있다. 이 때 문장의 끝에 세미콜론(;)를 붙이지 않아야 한다
(String name, int i) -> { System.out.println(name + " = " + i); } (String name, int i) -> System.out.println(name + " = " + i)
5. 괄호{} 안의 문장이 return문일 경우 괄호{}를 생략할 수 없다.
(int a, int b) -> { return a > b ? a : b; } // return문 Ok. (int a, int b) -> return a > b ? a : b // return문 error. (int a, int b) -> a > b ? a : b // 식(expression)
함수형 인터페이스
함수형 인터페이스란 오직 하나의 추상 메서드만 가지는 인터페이스를 말한다. 람다식을 처음 접하면 메서드와 동등한 것으로 생각하게 되지만 사실 람다식은 익명 클래스의 객체와 동등하다.
람다식으로 정의된 익명 객체의 메서드
1. 함수형 인터페이스 구현
interface MyFunction{ public abstract int max(int a, int b); }
2. 위에서 구현한 인터페이스를 익명 클래스의 객체로 생성
MyFunction f = new MyFunction(){ public int max(int a, int b){ return a > b ? a: b; } } int big = f.max(5, 3);
3. 익명 객체를 람다식으로 대체
MyFunction f = (int a, int b) -> a > b ? a : b; int big = f.max(5, 3); // 다양한 익명 객체의 메서드 사용 방법 MyFunction add = (int a, int b) -> {return a + b; }; MyFunction add1 = (int a, int b) -> a + b; MyFunction add2 = Integer::sum;
이처럼 MyFunction 인터페이스를 구현한 익명 객체를 람다식으로 대체 가능한 이유는 람다식도 실제로는 익명 객체이고 MyFunction 인터페이스를 구현한 익명 객체의 메서드 max()와 람다식의 매개변수의 타입과 개수 그리고 반환값이 일치하기 때문에 가능하다.
하나의 메서드가 선언된 인터페이스를 정의하여 람다식을 다루면 기존의 자바 규칙을 어기지 않으면서 자연스럽게 다룰 수 있다. 그래서 인터페이스를 통해 람다식을 다루기로 결정했고, 람다식을 다루기 위한 인터페이스를 ‘함수형 인터페이스(functional interface)’라고 부른다.
참고
@FunctionalInterface람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 떄문에 함수형 인터페이스는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다. 이러한 제약조건을 잘 지켰는지 컴파일러가 확인하도록 @Functionalinterface를 사용하는 것이 좋다. (static 메서드와 default 메서드의 개수는 제약이 없다.)
Variable Capture
람다식에서 외부 지역변수를 참조하는 행위를 Lambda Capturing이라고 한다. 람다에서 접근가능한 변수는 아래와 같이 세가지 종류가 있다.
- 지역 변수
- static 변수
- 인스턴스 변수
람다식에서 외부 지역변수를 참조할 때 위에서 지역변수만 변경이 불가능하고 나머지 변수들은 읽기 및 쓰기가 가능하다. 람다는 지역 변수가 존재하는 스택에 직접 접근하지 않고, 지역 변수를 자신의 스택에 복사한다. 따라서 각각의 쓰레드마다 고유한 스택을 갖고 있어서 지역 변수가 존재하는 쓰레드가 사라져도 람다는 복사된 값을 참조하면서 에러가 발생하지 않는다.
하지만 멀티 쓰레드 환경에서는 문제가 발생한다. 여러 개의 쓰레드에서 람다식을 사용하면서 람다 캡처링이 계속 발생하는데 이 때 외부 변수 값의 불변성을 보장하지 못하면서 동기화(sync) 문제가 발생한다. 이러한 문제로 인해 지역변수는 final, Effectively Final 제약조건을 갖게 된다.
인스턴스 변수나 static 변수는 스택 영역이 아닌 힙 영역에 위치하고, 힙 영역은 모든 쓰레드가 공유하는 메모리 영역이기 때문에 값의 쓰기가 발생하여도 별 문제가 없게 된다.
메서드, 생성자 레퍼런스
메서드 레퍼런스
람다식으로 메서드를 간결하게 표현할 수 있는데, 놀랍게도 람다식을 더욱 간결하게 표현할 수 있는 방법이 있다. 람다식이 하나의 메서드만 호출하는 경우에 ‘메서드 참조(method reference)’라는 방법으로 간략히 할 수 있다.
메서드 참조하는 방법
- 스태틱 메소드 참조 → 타입::스태틱 메소드
- 특정 객체의 인스턴스 메소드 참조 → 객체 래퍼런스::인스턴스 메소드
- 임의 객체의 인스턴스 메소드 참조 → 타입::인스턴스 메소드
- 생성자 참조 → 타입::new
BiFunction<String, String, Boolean> f = (s1, s2) -> s1.equals(s2);
위 람다식을 메서드 참조로 변경하면 다음과 같다.
BiFunction<String, String, Boolean> f = String::equals;
s1과 s2를 생략하고나면 equals만 남는데, 두 개의 String을 받아서 Boolean으로 반환하는 equals라는 이름의 메서드는 다른 클래스에도 존재할 수 있기 때문에 equals 앞에 클래스 이름이 반드시 필요하다.
static 메서드 참조
메서드 참조는 static 메서드를 직접적으로 가리킬 수 있다.
클래스이름::메서드이름 (매개변수) -> Class.staticMethod(매개변수) String::valueOf str -> String.valueOf(str)
인스턴스 메서드 참조
클래스이름::메서드이름 (obj, 매개변수) -> obj.instanceMethod(매개변수) String::length (value) -> value.length();
특정 객체 인스턴스 메서드 참조
특정 인스턴스의 메서드를 참조, 클래스 이름이 아닌 인스턴스 명을 넣는다
obj::instanceMethod (매개변수) -> obj.instanceMethod(매개변수) object::toString () -> object.toString()
생성자의 메서드 참조
생성자를 호출하는 람다식도 메서드 참조로 변환할 수 있다.
Supplier<MyClass> s = () -> new MyClass(); Supplier<MyClass> s = MyClass::new;
매개변수가 필요한 생성자라면, 매개변수의 개수에 따라 알맞은 함수형 인터페이스를 사용하면 된다. 필요에 따라 함수형 인터페이스를 새로 정의해야 한다.
Function<Integer, MyClass> f = (i) -> new MyClass(i); Function<Integer, MyClass> f2 = MyClass::new;
728x90반응형'Java > java study' 카테고리의 다른 글
Java - 제네릭(Generics) (0) 2022.04.03 Java - Input과 Output(I/O) (0) 2022.03.27 Java - 애노테이션(annotation) (0) 2022.03.06 Java - enum이란 (0) 2022.02.27 Java - Thread (0) 2022.02.20