1. 람다식(Lambda expression)
람다식의 도입으로 인해, 자바는 객체지향언어인 동시에 함수형 언어가 됨.
1.1 람다식이란?
메서드를 하나의 '식'으로 표현한 것.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 '익명 함수'라고도 한다.
() -> (int)(Math.random() * 5) + 1
위와 같은 람다식을 메서드로 표현하면 아래와 같다.
int method() {
return (int)(Math.random() * 5) + 1;
}
메서드를 사용하려면 클래스도 만들고, 객체를 생성해야만 하는데, 람다식은 그 자체만으로 메서드의 역할을 할 수 있는 것이다.
또한, 람다식은 메서드의 매개변수로 전달되는 것이 가능하고, 메서드의 결과로 반환될 수도 있어서, 람다식 덕분에 메서드를 변수처럼 다루는 것이 가능해졌다!
1.2 람다식 작성하기
'익명함수'답게 메서드에서 반환타입과 이름을 제거하고, 매개변수 선언부와 몸통{} 사이에 에로우 '->'를 추가한다.
(int a, int b) -> {
return a > b ? a : b;
}
또한 return문 대신 '식'으로 대신할 수 있는데 식의 연산결과가 자동으로 반환값이 된다. 주의할 점은 '식'은 문장이 아니므로 끝에 ';'를 붙이지 않는다.
(int a, int b) -> a > b ? a : b
게다가 람다식에 선언된 매개변수의 타입이 추론이 가능한 경우라면 생략될 수 있다.
(a, b) -> a > b ? a : b
선언된 매개변수가 하나뿐인 경우에는 괄호()마저 생략할 수 있다. 단, 나입이 있는 매개변수는 괄호를 생략해서는 안 된다.
a -> a * a
마찬가지로 중괄호 {} 안의 문장이 하나일 때, 괄호를 생략할 수 있고 문장의 끝에 ';'를 붙이면 안 된다.
(String name, int i) -> System.Out.println(name + "=" + i)
💥 괄호 {} 안의 문장이 return문일 경우에는 괄호를 생략해서는 안 된다.
실습 예제
List<String> list = Arrays.asList(new String[] {"고길동", "홍길동", "일길동", "========"});
list.forEach((String s) -> {System.out.println(s);});
list.forEach((String s) -> System.out.println(s));
list.forEach((s) -> System.out.println(s));
list.forEach(s -> System.out.println(s));
1.3 함수형 인터페이스
람다식은 익명 클래스의 객체와 동등하다. 람다식을 다루기 위한 인터페이스를 '함수형 인터페이스'라고 부른다.
LamdaExPrev.java
package chap14;
import java.util.Arrays;
import java.util.List;
public class LamdaExPrev {
public static void main(String[] args) {
Object obj = new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
};
MyFunction mf = new MyFunction() {
@Override
public int max(int a, int b) {
// TODO Auto-generated method stub
return a > b ? a : b;
}
};
MyFunction mf2 = (a, b) -> a > b ? a : b;
System.out.println(mf.max(3, 5));
System.out.println(mf2.max(5, 2));
System.out.println(mf);
System.out.println(mf2);
}
}
interface MyFunction {
int max(int a, int b);
}
🔷 인터페이스 MyFunction을 아래에 정의해 뒀기 때문에, 메인 메서드에서 인터페이스를 구현한 익명 클래스의 객체를 생성할 수 있다. MyFunction 객체를 생성하면 자동으로 익명클래스가 오버라이딩 된다.
🔷 mf2는 익명 객체를 람다식으로 간단하게 표현한 것이다.
🔷 interface 위에 @FunctionalInterface를 붙이면 더 확실하게 함수형 인터페이스임을 알려줄 수 있는데, 단 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의 되어 있어야 한다.
- 함수형 인터페이스 타입의 매개변수와 반환타입
메서드의 매개변수가 함수형 인터페이스 타입이면, 이 메서드를 호출할 때 람다식을 참조하는 참조변수를 매개변수로 지정해야 한다.
LamdaEx01.java
package chap14.ex01;
@FunctionalInterface
interface MyFunction {
void run();
}
public class LamdaEx01 {
static void execute(MyFunction f) {
f.run();
}
static MyFunction getMyFunction() {
return () -> System.out.println("f3.run()");
}
public static void main(String[] args) {
MyFunction f1 = () -> System.out.println("f1.run()");
MyFunction f2 = new MyFunction() { // 익명클래스
@Override
public void run() {
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
execute(f1);
execute(() -> System.out.println("run()"));
}
}
🔷 파라미터로 MyFunction 타입이 왔는데 다형성에 의해 MyFunction의 조상 클래스가 오면 된다. (다형성: 조상 클래서의 타입을 가진 참조변수에 자손 인스턴스가 들어갈 수 있는 것!)
🔷 f1은 람다식 형태, f2는 익명 클래스 형태.
🔷 자바스크립트의 콜백함수 같은 형태
- 람다식의 타입과 형변환
람다식은 익명 객체이고 익명 객체는 타입이 없다. 그래서 아래와 같이 형변환이 필요하다.
람다식은 MyFunction 인터페이스를 직접 구현하지 않았지만, 이 인터페이스를 구현한 클래스의 객체와 완전히 동일하기 때문에 이와 같은 형변환이 허용되고, 이 형변환은 생략도 가능하다.MyFunction f = (MyFunction)(() -> {});
💥 주의할 점은 람다식은 분명히 객체이지만, Object 타입으로 형변환 할 수 없다는 것이다. 람다식은 오직 함수형 인터페이스로만 형변환이 가능하다.
LamdaEx02.java
package chap14.ex02;
interface MyFunction {
void run();
}
public class LamdaEx02 {
public static void main(String[] args) {
MyFunction f = () -> {}; // 다형성 묵시적 형변환으로 (MyFunction)이 생략되어 있음.
Object obj = (MyFunction)() -> {}; // Object는 뭐든 들어갈 수 있지만 단 하나, 람다식은 못 들어감.
String str = ((MyFunction)() -> {}).toString(); // 투스트링은 오브젝트에 있는 메서드.
System.out.println(f);
System.out.println(obj);
System.out.println(str);
System.out.println((MyFunction)() -> {});
System.out.println(((MyFunction)() -> {}).toString());
}
}
- 외부 변수를 참조하는 람다식
익명클래스의 특징 중 하나는, 익명클래스가 갖는 특정 메서드의 파라미터는 상수여야만 한다는 것이다.
LamdaEx03.java
package chap14.ex03;
import chap14.ex03.Outer.Inner2;
@FunctionalInterface
interface MyFunction {
void myMethod();
}
class Outer {
int val = 10; // Outer.this.val
class Inner {
int val = 20; // this.val
void method(int i) { // void method(final int i
int val = 30 ; // final int val = 30;
MyFunction f = () -> {
System.out.println(" i : " + i);
System.out.println(" val : " + val);
System.out.println(" this.val : " + ++this.val);
System.out.println("Outer.this.val : " + ++Outer.this.val);
};
f.myMethod();
}
} // Inner 끝
static class Inner2 {
}
} // Outer 끝
public class LamdaEx03 {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // static 키워드가 안 붙어서 .으로 접근. 객체로부터 생성
Inner2 inner2 = new Inner2(); // 클래스로부터 생성
inner.method(100);
}
}
🔷 람다식 내에서 외부에 선언된 변수에 접근하는 방법을 보여주는 예제다.
🔷 람다식 내에서 참조하는 지역변수는 final이 붙지 않아도 상수로 간주된다.
🔷 그렇기 때문에 람다식 내에서나 다른 곳에서 이 변수들의 값을 변경할 수 없다.
🔷 한편, Inner클래스와 Outer 클래스의 인스턴스 변수인 this.val과 Outer.vhis.val은 상수로 간주되지 않으므로 값을 변경할 수 있다.
🔷 클래스 앞에 static이 붙을 수 있는 것은 내부 클래스만 가능하다.
'JAVA' 카테고리의 다른 글
자바로 쇼핑몰 상품 이미지 크롤링 하기. (jsoup 라이브러리 이용) 21. 05. 30. (0) | 2021.05.30 |
---|---|
java.util.function 패키지 21. 05. 06. (0) | 2021.05.07 |
통합 구현 (JSON을 이용한 파싱) 21. 03. 03. (0) | 2021.03.03 |
07장- 객체지향 프로그래밍Ⅱ(6. 추상클래스, 7. 인터페이스) 21. 02. 24. (0) | 2021.02.24 |
09장- java.lang패키지와 유용한 클래스 21. 02. 21. (0) | 2021.02.21 |