개발/Java Study

15주차: 람다식

박비버 2021. 3. 21. 21:50

1. 람다식 사용법

  • 익명 함수 (Anonymous function)
  • 모든 메서드는 클래스에 포함되어야 하므로 클래스도 만들어야하고, 객체도 생성해야만 호출이 가능한데, 람다식을 사용하면 메서드의 역할을 대신 할 수 있다
  • 람다식은 메서드의 매개변수로 전달되어지는 것이 가능
반환타입 메서드 이름 (매개변수 선언) {
    문장들
}

// 아래와 같이 표현 가능

(매개변수 선언) -> {
    문장들
}
메서드 람다식
int max (int a, int b) {
    return a > b ? a : b;
}
(int a, int b)  -> { return a > b ? a : b; }
(int a, int b) -> a > b ? a : b
(a, b) -> a > b ? a : b
void printVar (String name, int i) {
    System.out.println(name + "="+i );
}
 (String name, int i) ->
    {
System.out.println(name + "="+i ); }
(name, i) ->
     { System.out.println(name + "="+i ); }
(name, i) ->
    System.out.println(name + "="+i )
int suqare (int x) {
    return x + x;
}
(int x) -> x * x
(x) -> x * x
x -> x * x
int roll() {
    return (int) (Math.random()*6);
}
() -> { return (int) (Math.random()*6); } 
() -> (int) (Math.random() * 6)
int sumArr(int[] arr) {
    int sum = 0;
    for (int i : arr)
        sum += i;
    return sum;
}
(int[] arr) -> {
    int sum = 0;
    for (int i : arr)
        sum += i;
    return sum;
}

 

2. 함수형 인터페이스

  • 람다식은 익명클래스의 객체와 동등하다
타입 f = (int a, int b) -> a > b ? a : b;
MyFunction f = new MyFunction() { 
    public int max(int a, int b) {
    	return a > b ? a : b;
    }
}

int big = f.max(5,3);


// 아래와 같이 바꿀 수 있음
MyFunction f = (int a, int b) -> return a > b ? a : b;
int big = f.max(5,3);
  • 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다
  • static 메서드나 default 메서드의 개수에는 제약이 없다
  • @FunctionInterface를 붙이면 컴파일러가 인터페이스를 올바르게 정의하였는지 확인해 준다!

함수형 인터페이스 타입의 매개변수와 반환 타입

  • 람다식을 참조 변수로 다룰 수 있다 -> 메서드를 통해 람다식을 주고받을 수 있다
void aMethod(MyFunction f) {
    f.myMethod();
}
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);

// 직접 람다식을 매개변수로 넣을 수도 있음

aMethod( () -> System.out.println("myMethod()");

 

람다식의 타입과 형변환

  • 람다식의 타입은 '외부클래스이름$$Lambda$번호'와 같은 형식으로 되어있다
  • 람다식은 Object형으로 변환할 수 없다, 함수형 인터페이스로만 형변환이 가능하다
@FunctionalInterface
interface MyFunction {
	void myMethod();  // public abstract void myMethod();
}

class LambdaEx2 {
	public static void main(String[] args) 	{
		MyFunction f = ()->{}; // MyFunction f = (MyFunction)(()->{}); 
		Object obj = (MyFunction)(()-> {});  // Object타입으로 형변환이 생략됨
		String str = ((Object)(MyFunction)(()-> {})).toString();

		System.out.println(f);
		System.out.println(obj);
		System.out.println(str);

//		System.out.println(()->{});	// 에러. 람다식은 Object타입으로 형변환 안됨
		System.out.println((MyFunction)(()-> {}));
//		System.out.println((MyFunction)(()-> {}).toString()); // 에러
		System.out.println(((Object)(MyFunction)(()-> {})).toString());
	}
}

 

외부 변수를 참조하는 람다식

  • 람다식 내에서 참조하는 지역변수는 final 이 붙지 않아도 상수로 간주된다
  • Inner 클래스와 Outer 클래스의 인스턴스 변수는 상수로 간주되지 않으므로 값을 변경할 수 있다
@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;
//			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();
		}
	} // Inner클래스의 끝
} // Outer클래스의 끝

class LambdaEx3 {
	public static void main(String args[]) {
		Outer outer = new Outer();
		Outer.Inner inner = outer.new Inner();
		inner.method(100);
	}
}

// 결과값은 ..?

 

3. Variable Capture

  • 익명 함수 내에서는 매개변수, 지역 변수에는 final이 붙은 변수만 사용 할 수 있다
  • 클래스 내 매개 변수, 지역변수는  JVM stack 영역에 할당된다, 이 지역에 할당된 변수들은 사용이 끝나면 사라진다
  • 그런데 익명함수는 JVM heap 영역에 할당이 되고, heap 영역은 GC(Garbage collector) 에 의해서 관리 된다.
  • 그래서 클래스 내 매개변수, 지역변수를 익명 함수를 익명함수 내에서 사용하게 되면 사용이 끝나고 사라진 변수를 호출하려고 할 수 있다 (오류!)
  • 익명 함수 내에서는 final이 붙은 변수만 사용할 수 있도록 해서, 변경이 불가능한 변수만 허용한 것! 변경될 일이 없는 경우 final 생략 가능 (java 1.8 부터)
  • 요 변경이 불가능한 변수의 값을 복사해서 해결하고 있다 = variable capture

 

4. 메소드, 생성자 레퍼런스

메소드 참조

Function<String, Integer> f = (String s) -> Integer.parseInt(s); Function<String, Integer> f = Integer::parseInt;
BiFunction<String, String, Boolean>  f = (s1, s2) -> s1.equals(s2); BiFunction<String, String, Boolean> f = String::equals;

 

종류 람다 메서드 참조
static 메서드 참조 (x) -> ClassName.method(x) ClassName::method
인스턴스메서드 참조 (obj, x) -> obj.method(x) ClassName::method
특정 객체 인스턴스메서드 참조 (x) -> obj.method(x) obj::method

생성자 메서드 참조

Supplier<MyClass> s = () new MyClass(); Supplier<MyClass> s = MyClass::new;
Function<Integer, MyClass> f = (i) -> new MyClass(i) Function<Integer, MyClass> f2 = MyClass::new;
BiFunction<Integer, String, MyClass> bf = (i,s) -> new MyClass(i, s) BiFunction<Integer, String MyClass> bf2 = MyClass::new; 
Function<Integer, int[]> f = x -> new int[x]; Function<Integer, int[]> f2 = int[]::new;

 

 

출처

자바의 정석, 남궁 성 지음

blog.naver.com/hsm622/222260183401