개발/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; |
출처
자바의 정석, 남궁 성 지음