int[] arr = new int[5];
int method() {
return (int)(Math.random() * 5) + 1;
}
🔺 method()를 람다식으로 표현하면 하기와 같다.
int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int)(Math.random() * 5) + 1);
🔺 보다 간결해지고 이해하기 쉬워졌다.
람다식은 수학자 알론조(Alonzo Church)가 발표한 람다 계산법에서 사용된 식으로, 이를 제자 존 매카시(John Macarthy)가 프로그래밍 언어에 도입하여 탄생했고 Java 8 버전부터 람다식을 지원하게 되었다.
람다식은 익명함수(anonymous function)을 생성하기 위한 식으로 객체 지향 언어보다는 함수 지향 언어에 가깝다.
이전에도 함수형 프로그래밍 언어들이 있었지만, 학계에서만 호응받고 말았는데 최근에 병렬처리와 이벤트 지향 프로그래밍에 적합하고 딥러닝이나 빅데이터등 분야에서 일종의 문법적 트렌드처럼 관심을 받기 시작하고 있다.
장점
단점
->
를 추가한다.반환타입 메서드이름(매개변수 선언) {
문장들
}
⇩
반환타입 메서드이름(매개변수 선언) -> {
문장들
}
예시
메서드 max()를 람다식으로 변환하기
int max(int a, int b) {
return a > b ? a : b;
}
(int a, int b) -> {
return a > b ? a : b;
}
🔺 반환값이 있는 메서드의 경우 return문도 람다식으로 대체할 수 있다.
(int a, int b) -> { return a > b ? a : b; } // return문
(int a, int b) -> a > b ? a : b // 식(expression)
🔺 이때는 문장(return문)이 아닌 식이므로 끝에 세미콜론(;)을 붙이지 않는다.
(int a, int b) -> a > b ? a : b
(a, b) -> a > b ? a : b // 매개변수의 타입 생략
🔺 2개 이상의 매개변수 중 어느 하나의 타입만 생략하는것은 불가하다.
(int a) -> a * a
a -> a * a // Ok.
int a -> a * a // error.
(String name, int i) -> {
System.out.println(name + " = " + i);
}
(String name, int i) ->
System.out.println(name + " = " + i)
(int a, int b) -> { return a > b ? a : b; } // return문 Ok.
(int a, int b) -> return a > b ? a : b // return문 error.
(int a, int b) -> a > b ? a : b // 식(expression)
(int a, int b) -> a > b ? a : b
// 위(람다식)와 아래(익명 클래스의 객체 내부 메소드)와 같다
new Object(){
int max(int a, int b){
return a > b ? a : b;
}
}
타입 f = (int a, int b) -> a > b ? a : b;
예시
max() 라는 메서드가 정의된 MyFunction 인터페이스가 정의되어 있다.
interface MyFunction{
public abstract int max(int a, int 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) -> a > b ? a : b; // 익명 객체를 람다식으로 대체
int big = f.max(5,3); // 익명 객체의 메서드를 호출
타입 f = (int a, int b) -> a > b ? a : b;
@FunctionalInterface
interface MyFunction {
public abstract int max(int a, int b);
}
@FunctionalInterface
애너테이션을 사용하여 에러를 방지하자. @FunctionalInterface
interface MyFunction {
void myMethod(); // 추상 메서드
}
void aMethod(MyFunction f) {
f.myMethod(); // MyFunction에 정의된 메서드 호출
}
...
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);
aMethod(() -> System.out.println("myMethod()"));
MyFunction myMethod() {
MyFunction f = () -> {};
return f;
// 위 두줄을 한줄로 줄이면, return () -> {};
}
@FunctionalInterface
interface MyFunction {
void method();
}
// 형변환
MyFunction mf = (MyFunction)(() -> {});
// 생략 가능함
MyFunction mf = (() -> {});
// 단, Object로의 형변환은 불가,
// 오직 함수형 인터페이스로만 형변환 가능
Object obj = (Object)(() -> {});
// Object로 굳이 형변환을 하고 싶다면?
// 함수형 인터페이스로 변환한 뒤에 가능하다.
Object obj = (Object)(MyFunction)(() -> {});
String str = ((Object)(MyFunction)(() -> {})).toString();
public class 클래스 {
멤버 필드;
멤버 메소드(매개변수 A){
지역변수 B;
생성한 객체 C = new C();
}
}
import java.util.function.*;
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() {
int baseNumber = 10; // 사실상 Final인 변수(effective final)
IntConsumer printInt = (i) -> {
System.out.println(i + baseNumber);
};
printInt.accept(10);
}
}
baseNumber
가 Final로 명시되어 있거나쉐도잉
하지 않는다.
import java.util.function.*;
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() {
int baseNumber = 10; // 사실상 Final인 변수(effective final)
//로컬 클래스
Class LocalClass {
void printBaseNumber() {
System.out.println(baseNumber);
}
}
//익명 클래스
Consumer<Integer> integerConsumer = new Consumer() {
@Override
public void accpet(Integer integer) {
System.out.println(baseNumber);
}
};
//람다
IntConsumer printInt = (i) -> {
System.out.println(i + baseNumber);
};
printInt.accept(10);
}
}
import java.util.function.*;
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() {
int baseNumber = 10; // 사실상 Final인 변수(effective final)
//로컬클래스
Class LocalClass { // 로컬클래스의 Scope 범위 시작
void printBaseNumber() {
int baseNumber = 11; //참조하고있는 외부 변수와 동일한 변수명을 스코프 내에서 재정의
System.out.println(baseNumber); // 10이 아닌 11이 출력된다.
}
} // 로컬클래스의 Scope 범위 끝
//익명클래스
Consumer<Integer> integerConsumer = new Consumer() { // 익명클래스의 Scope 범위 시작
@Override
public void accpet(Integer baseNumber) { // 파라미터 이름을 참조하고 있는 외부 변수와 동일한 이름으로 변경
System.out.println(baseNumber);
}
}; // 익명클래스의 Scope 범위 끝
printInt.accept(10);
}
}
import java.util.function.*;
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() { // 람다의 Scope 범위 시작
int baseNumber = 10; // 사실상 Final인 변수(effective final)
//람다
IntConsumer printInt = (baseNumber) -> { // 같은 변수명을 사용할 경우 컴파일 에러가 난다.
System.out.println(baseNumber);
};
printInt.accept(10);
} // 람다의 Scope 범위 끝
}
왜?? 다르지
익명클래스는 익명 "객체"로 볼 수 있지만, 람다는 객체가 아니여서 그렇다.
즉, 익명클래스 내에서 this는 자기 자신을 가리키지만 람다에서의 this는 자신을 선언한 클래스를 말한다.
Integer wrapper(String s) {
return Integer.parseInt(s);
}
Integer.parseInt()
에게 넘겨주는 역할만 한다. 이를 람다식과 메서드 참조로 바꿔보면 다음과 같다.람다식
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
메서드 참조
Function<String, Integer> f = Integer::parseInt;
Integer
의 parseInt()
는 static 메서드
이다. BiFunction<String, String, Boolean> f = (s1, s2) -> s1.equals(s2);
BiFunction<String, String, Boolean> f = String::equals;
MyClass obj = new MyClass();
Function<String, Boolean> f = (x) -> obj.equals(x); // 람다식
Function<String, Boolean> f2 = obj::equals; // 메서드 참조
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
Function<Integer, MyClass> f = (i) -> new MyClass(i);
Function<Integer, MyClass> f2 = MyClass::new;