What 람다란?
-람다식(Lambda expression)은 간단히 말해서 메서드를 하나의 '식(expression)'으로 표현한 것이다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환 값이 없어지므로, 람다식은 '익명함수(anonymous function)'이라고도 한다.
Why 사용 이유는?
-람다식의 도입으로 기존의 자바를 거의 변경하지 않고도 함수형 언어의 장점을 잘 접목하는데 성공했다.
-람다식으로 인해 메서드를 변수처럼 다루는 것이 가능해진 것이다. 람다식은 메서드의 매개변수로 전달되는 것이 가능하고, 메서드의 결과로 반환될 수도 있다.
-람다식은 간단하다. 모든 메서드는 클래스에 포함되어야 하므로 클래스도 새로 만들어야 하고, 객체도 생성해야만 비로소 이 메서드를 호출할 수 있다. 그러나 람다식은 이 모든 과정 없이 오직 람다식 자체만으로도 이 메서드의 역할을 대신할 수 있다.
How 어떻게 사용하는가?
-람다식은 '익명 함수'답게 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 '->'를 추가한다.
반환타입 메서드이름(매개변수 선언){
문장들
}
int max(int a, int b){
return a > b ? a : b;
}
↓
(매개변수 선언){
문장들
}
(int a, int b){
return a > b ? a : b;
}
-반환 값이 있는 메서드의 경우, return문 대식 '식(expression)'으로 대신 할 수 있다. 식의 연산 결과가 자동적으로 반환값이 된다. 이때는 '문장(statement)'이 아닌 '식'이므로 끝에 ';'를 붙이지 않는다.
(int a, int b) -> {return a>b ? a : b;}
↓
(int a, int b) -> a>b ? a : b
-람다식에 선언된 매개변수의 타입은 추론이 가능한 경우는 생략할 수 있다. 람다식에 반환 타입이 없는 이유도 항상 추론이 가능하기 때문이다.
단, '(int a, b) -> a > b ? a : b' 와 같이 두 매개변수 중 어느 하나의 타입만 생략하는 것은 허용되지 않는다.
(int a, int b) -> a>b ? a : b
↓
(a, b) -> a>b ? a : b
-람다식에 선언된 매개변수가 하나뿐인 경우에는 괄호()를 생략할 수 있다.
단, 매개변수의 타입이 있으면 괄호를 생략할 수 없다.
ex) int a -> a * a // 에러
(a) -> a * a
↓
a -> a * a
-괄호{} 안의 문장이 하나일 때는 괄호{}를 생략할 수 있다. 이 때 문장의 끝에 ';'을 붙이지 않아야 한다.
단, 괄호{} 안의 문장이 return문일 경우 괄호{}를 생략할 수 없다.
ex) (int a, int b) -> return a > b ? a : b //에러
(String name, int i) -> {System.out.println(name+"="+i);}
↓
(String name, int i) -> System.out.println(name+"="+i)
-람다식은 익명 클래스의 객체와 동등하다. 기존에는 인터페이스의 메서드 하나를 구현하는데도 익명 클래스를 사용하여 복잡했던 코드를 아래와 같이 람다식으로 간단히 처리할 수 있게 되었다.
List<String> list = Arrays.asList("abc","aaa","bbb","ddd","aaa");
/*익명 클래스를 이용한 정렬 */
Collections.sort(list, new Comparator<String>() {
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
});
/*람다를 이용한 정렬*/
Collections.sort(list);
-람다식을 다루기 위한 인터페이스를 '함수형 인터페이스(functional interface)'라고 한다. 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다. 그래야 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 때문이다. (static,default 메서드의 개수 제약은 없다.)
@FunctionalInterface
interface MyFunction{
public abstract void myMethod();
}
-메서드의 매개변수가 함수형 인터페이스라면, 이 메서드를 호출할 때 람다식을 참조하는 참조 변수를 매개변수로 지정해야한다는 뜻이다. 또는 참조변수 없이 직접 람다식을 매개변수로 지정할 수도 있다.
메서드의 반환타입이 함수현 인터페이스라면, 이 함수형 인터페이스의 추상메서드와 동등한 람다식을 가리키는 참조변수를 반환하거나 람다식을 직접 반환할 수 있다.
@FunctionalInterface
interface MyFunction{
public abstract void myMethod();
}
public class LambdaTest {
public static void main(String[] args) {
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f); //람다식을 참조하는 참조변수를 매개변수로 지정
aMethod(()->System.out.println("myMethod()")); //참조변수 없이 직접 람다식을 매개변수로 지정
}
static void aMethod (MyFunction f) { //매개변수의 타입이 함수형 인터페이스
f.myMethod(); //함수형인터페이스에 정의된 메서드 호출
}
}
사실상 메서드가 아니라 객체를 주고 받는 것이라(람다식은 익명클래스의 객체와 동등하므로) 근복적으로는 달라진 것은 아무것도 없지만, 람다식 덕분에 예전보다 코드가 더 간결하고 이해하기 쉬워졌다.
-람다식은 익명 객체이고, 익명 객체는 타입이 없다. 정확히는 타입은 있지만 컴파일러가 임의로 이름을 정하기 때문에 알 수 없다. 그래서 대입 연산자의 양변의 타입을 일치시키기 위해 형변환이 필요하다.
MyFunction f = (MyFunction)()->{};
위와 같이 람다식은 오직 함수형 인터페이스로만 형변환 가능하며, 함수형 인터페이스로의 형변환은 생략가능하다. Object타입으로 형변환하려면, 먼저 함수형 인터페이스로 변환후 진행해야한다.
Object obj = (MyFunction)(()->{});
// Object obj2 = (Object)(()->{}); // 에러
@FunctionalInterface
interface MyFunction{
void myMethod();
}
public class LambdaTest2 {
public static void main(String[] args) {
MyFunction f = (MyFunction)()->{};
Object obj = (MyFunction)(()->{});
// Object obj2 = (Object)(()->{}); // 에러
String str = ((Object)(MyFunction)(()->{})).toString();
System.out.println(f);
System.out.println(obj);
System.out.println(str);
// System.out.println((MyFunction)(()->{}).toString()); //에러
}
}
결과
test1.LambdaTest2$$Lambda$1/303563356@e9e54c2
test1.LambdaTest2$$Lambda$2/1044036744@65ab7765
test1.LambdaTest2$$Lambda$3/1826771953@53d8d10a
-람다식은 익명 클래스의 인스턴스이므로 람다식에서 외부에 선언된 변수에 접근하는 규칙도 익명 클래스의 인스턴스와 동일한다.
1.람다식 내에서 참조하는 지역변수는 final이 붙지 않았어도 상수로 간주한다. 반면에 클래스의 멤버변수는 값을 변경할 수 있다.
2. 외부 지역변수와 같은 이름의 람다식 매개변수는 허용되지 않는다.
@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(fianl int i)
int val=30; //final int val=30;
//i=10;//에러. 상수의 값을 변경할 수 없음.
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();
}
}
}
public class LambdaTest3 {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.method(100);
}
}
결과
i :100
val :30
this.val :21
Outer.this.val :11
참조
자바의 정석
'자바' 카테고리의 다른 글
[Java] 자바의 제어문2, 조건문 (2) | 2020.12.12 |
---|---|
[Java] 자바의 제어문1, 반복문 (0) | 2020.12.12 |
[Java] 람다(Lambda)와 java.util.function패키지 (0) | 2020.04.17 |
[Java] super - 조상클래스 참조하기 (0) | 2020.04.06 |
[Java] Java의 탄생 (0) | 2019.03.28 |
댓글