본문 바로가기
자바

[Java] 람다(Lambda)

by 책 읽는 개발자_테드 2020. 4. 14.
반응형

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

 

 

 

참조

자바의 정석

 

반응형

댓글