람다식
- 자바는 객체지향 프로그래밍 언어이다. 그렇기 때문에 기능을 수행하기 위해서는 객체를 만들고 그 객체 내부에 멤버 변수를 선언하고, 그 기능을 수행하는 메소드를 구현해야한다.
- 하지만 자바 8부터는 함수형 프로그래밍 방식을 지원하고 이를
람다식
이라고 한다. - 함수의 구현과 호출만으로 프로그래밍이 수행돠는 방식이다.
함수형 프로그래밍의 장점
- 함수형 프로그래밍은 순수함수를 구현하고 호출하기 때문에 외부 자료에 부수적인 영향(side effect) 를 주지 않도록 구현하는 방식이다.
- 여기서 순수함수란? -> 매개변수만을 사용하여 만드는 함수를 말한다.
- 함수를 기반으로 하는 프로그래밍이고 입력받는 자료 이외에 외부 자료를 사용하지 않아 여려 자료가 동시에 수행되는 병렬처리가 가능하다.
- 함수형 프로그래밍은 함수의 기능이 자료에 독립적임을 보장한다.
- 이는 동일한 자료에 대해 동일한 결과를 보장하고, 다양한 자료에 대해 같은 기능을 수행할 수 있다.
람다 표현식
- 람다
- (인자 리스트) -> {바디}
- ex>
Supplier<Integer> get10 = () -> 10; // 입력값 없고 10을 리턴한다. BiFunction<Integer, Integer, Integer> get10 = (a, b) -> a + b; // a 와 b 를 입력받아서 a+b를 리턴
- 인자 리스트
- 인자가 없을 때: ()
- 인자가 한개일 때: (one) 또는 one
- 인자가 여러개 일 때: (one, two)
- 인자의 타입은 생략 가능, 컴파일러가 추론(infer)하지만 명시할 수도 있다. (Integer one, Integer two)
- 바디
- 화상표 오른쪽에 함수 본문을 정의한다.
- 여러 줄인 경우에 { }를 사용해서 묶는다.
- 한 줄인 경우에 생략 가능, return도 생략 가능.
- 변수 캡처 (Variable Capture)
private void run() {
int baseNumber = 10; // effective final -> 사실상 final
//로컬 클래스
class LocalClass {
void printBaseNumber() {
int baseNumber = 11; // 쉐도윙 된다.
System.out.println(baseNumber); // 11
}
}
//익명 클래스
Consumer<integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber);
}
};
//람다
IntConsumer printInt = (i) -> { // 만약 i 자리에 baseNumber가 오면 오류가 나온다. -> 쉐도잉이 안되기 때문이다.
System.out.println( i + baseNumber);
};
}
- 로컬 변수 캡처
final
이거나 effective final 인 경우에만 참조할 수 있다.- 그렇지 않을 경우 concurrency 문제가 생길 수 있어서 컴파일가 방지한다.
- effective final
- 이것도 역시 자바 8부터 지원하는 기능으로
“사실상" final인 변수.
- final 키워드 사용하지 않은 변수를 익명 클래스 구현체 또는 람다에서 참조할 수 있다.
- 이것도 역시 자바 8부터 지원하는 기능으로
- 익명 클래스 구현체와 달리 ‘쉐도윙’하지 않는다.
- 익명 클래스는 새로 스콥을 만들지만, 람다는 람다를 감싸고 있는 스콥과 같다.
코드 예시
@FuctionalInterface
public interface Add {
public int add (int a, int b);
}
- 위와 같이 Add 라는 functional interface 를 선언한다.
- @FuctionalInterface 가 붙으면 메소드를 추가할 수 없다. -> 람다식으로 호출할 메소드가 모호해지기 때문이다.
public class Main {
public static void main(String[] args){
Add add = (x, y) -> {return x+y;};
System.out.println(add.add(2,3));
}
}
- 위와 같이 반환 값을 Add 인터페이스로 받아서 실행 시킬 수 있다.
- Add add = (x, y) -> {return x+y;}; 는
Add add = (x, y) -> x+y;
로 생략해서 사용할 수 있다. - 자바는 어떤 방식으로 람다식 기능을 제공하고 있을까? -> 익명 내부 클래스로 제공하고 있다.
public class Main {
public static void main(String[] args){
Add add = (x, y) -> {return x+y;};
System.out.println(add.add(2,3));
// 익명 내부 클래스
Add add2 = new Add(){
@Override void add(int a, int b) {
return a+b;
}
}
}
}
- 내부적으로 위와 같이 익명 내부 클래스가 생성되고, 그 익명 내부 클래스가 호출되는 것이다.
- 위에서 구현한 람다식은 변수와 같이 매개변수와 반환값으로도 사용될 수 있다.
메소드 레퍼런스
- 람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있다.
- 스태틱 메소드 참조
- 타입::스태틱 메소드
- ex>
public class Greeting {
private String name;
public Greeting(){
}
public Greeting(String name){
this.name = name;
}
public String hello(String name){
return "hello " + name;
}
public Static String hi(String name){
return "hi" + name;
}
}
UnaryOperator<String> hi = Greeting::hi; //static 메서드 참조
- 특정 객체의 인스턴스 메소드 참조
- 객체 레퍼런스::인스턴스 메소드
- ex>
Greeting greeting = new Greeting();
UnaryOperator<String> hello = greeting::hello;
System.out.println(hello.apply("hyunwook"));
- 임의 객체의 인스턴스 메소드 참조
- 타입::인스턴스 메소드
- 생성자 참조
- 타입::new
- ex>
Supplier<Greeting> newGreeting = Greeting::new;
Greeting greeting = newGreeting.get(); // 이 시점에 객체가 생성된다.
- 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다.
- ex>
Function<String, Greeting> newGreeting = Greeting::new;
Greeting greeting = newGreeting.apply("hyunwook");
System.out.println(greeting.getName()); // "hyunwook" 이 출력된다.
- 입력값이 있는 생성자는 Function으로 받는다.
- 리턴값 또는 생성한 객체는 람다의 리턴값이다.
REFERENCES
- 백기선님의 더 자바, Java 8
'Java' 카테고리의 다른 글
인터페이스 기본 메소드와 스태틱 메소드 (0) | 2022.02.26 |
---|---|
상수와 리터럴 (0) | 2022.02.26 |
내부 클래스 (0) | 2022.02.26 |
Thread (0) | 2022.02.26 |
String,StringBuilder,StringBuffer (0) | 2022.02.26 |