📔 학습한 내용을 정리하기 위해 작성하는 게시글입니다.
람다식(Lambda expression
)이란 메서드를 하나의 식으로 표현한 것이다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 익명함수(anonymous function
)라고도 한다.
람다식(Lambda expression
)은 객체 지향 언어인 자바에서 함수형 프로그래밍을 가능하게 해주는 자바의 문법 요소이다.
Java8버전부터 람다식을 지원한다.
함수형 인터페이스(functional interface
)란 단 하나의 추상 메서드만을 포함하는 인터페이스이다.
@FunctionalInterface 애노테이션
- 함수형 인터페이스라는 의미이고, 메서드를 하나 이상 선언하면 오류가 난다.
@FunctionalInterface
가 적용된 인터페이스는 하나의 추상 메소드만 선언할 수 있다.
@FunctionalInterface
interface Unit11{
String move();
//void attack();
}
public class Ex11_Functional {
public static void main(String[] args) {
Unit11 unit = () -> {return "인간형 유닛 이동";};
System.out.println(unit.move());
}
}
코드의 간결성 : 람다식의 사용으로 불필요한 반복문을 줄이고 복잡한 식을 단순하게 표현 가능
의도의 명확성 : 코드가 간결해져 개발자의 의도를 함축적이고 명확하게 나타낼 수 있음
지연연산(lambda lazy initialization, lambda deferred initialization) 수행 : 람다식은 지연연산을 수행함으로써 불필요한 연산 최소화 가능
람다식을 사용하면 클래스나 메서드를 만들 필요가 없이 필요한 순간에 1회용으로 익명함수를 정의하고 바로 호출하면 되므로 클래스의 초기화 필요없다. 즉, 항상 초기화를 하는 것이 아니라 필요할 때만 초기화를 하는 것
병렬 처리 가능 : 일반적으로 멀티스레드 형태로 구현되어 병렬 처리에 유리
병렬 처리란 쉬운 예로 하나의 일(프로그램)을 여러 명(프로세서)이서 나눠서 함으로 처리 속도를 올리는 걸 의미한다.
람다식의 호출이 까다로움(람다식을 호출할 때에는 직접 인터페이스에 선언된 메서드를 호출해야 함)
람다 Stream 사용 시 단순 반복문(for, while) 사용 시 성능이 떨어짐
람다식을 불필요하게 남용하면 비슷한 함수가 중복 생성되어 오히려 가독성이 떨어질 수 있음
람다식으로 만든 익명 함수는 재사용 불가능
디버깅이 어려움(람다는 기본적으로 익명 함수 기반이며, 익명 함수의 특징을 따름. 익명 함수 특성 상 디버깅 시 함수 콜 스택 추적이 어려움)
람다식이 무조건 좋은 건 아니므로 상황에 맞게 적절히 사용해야 한다.
람다식은 메서드에서 이름과 반환타입을 제거하고, 매개변수 선언부와 몸통 { } 사이에 ->를 추가한다.
리턴 타입 메서드명(입력매개변수) {
//메서드 내용
}
↓
(입력매개변수) -> {
//메서드 내용
}
① 중괄호 안의 실행문이 1개일 때 중괄호 생략 가능
A a1 = () -> {System.out.println("테스트"););
A a2 = () -> System.out.println("테스트");
② 매개변수 타입은 생략 가능하고, 매개변수가 1개일 때 ( ) 생략 가능
A a1 = (int a) -> {...};
A a2 = (a) -> {...};
A a3 = a -> {...};
A a4 = int a -> {...}; // X (소괄호가 생략될 때는 매개변수 타입을 반드시 생략)
③ return 구문만 포함할 때 return 생략 가능
A a1 = (int a, int b) -> {return a + b};
A a2 = (int a, int b) -> a + b;
A a3 = (int a, int b) -> {a + b;} // X (return을 생략할 때 중괄호를 반드시 함께 생략)
: 함수형 인터페이스의 객체 생성 과정에서 익명 내부 클래스를 이용한 객체 생성 방식의 축약된 표현을 제공(직접 추상 메서드를 구현하는 형태)
//함수형 인터페이스
interface A{
void method1();
}
↓
//익명 이너 클래스 활용
A a = new A() {
public void method1() {
...
}
};
↓
//람다식 활용
A a = () -> {...};
익명 내부 클래스 내부의 구현 메서드를 축약해 표현한 형태.
위 람다식 문법 내용을 참고
: 추상 메서드를 직접 구현하는 대신, 이미 구현이 완료된 메서드를 참조하는 것
메서드 참조를 위해서는 리턴 타입과 입력매개변수 타입이 동일해야 함
클래스 객체 :: 인스턴스 메서드명
인스턴스 멤버(필드, 메서드, 내부 클래스)는 인스턴스. 즉, 객체 내부에 존재하기 때문에 이를 사용하기 위해서는 항상 객체를 먼저 생성해야 함
[ abc() 추상 메서드 하나를 갖는 함수형 인터페이스 A와 인스턴스 메서드 bcd()를 갖고 있는 B 클래스가 정의된 경우 ]
interface A {
void abc();
}
class B {
void bcd() {
System.out.println("메서드");
}
}
① 익명 내부 클래스 및 람다식으로 각각 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public void abc() {
B b = new B();
b.bcd();
}
};
↓
//람다식
A a = () -> {
B b = new B();
b.bcd();
};
abc( ) 메서드 = B 객체의 bcd( ) 메서드
② 정의되어 있는 인스턴스 메서드 참조
B b = new B();
A a = b::bcd; //A 인터페이스 내부의 abc()메서드는 참조 변수 b
③ 코드
interface A {
void abc();
}
class B {
void bcd() {
System.out.println("메서드");
}
}
public class RefOfIntanceMethod_Type1_1 {
public static void main(String[] args) {
//1.익명 내부 클래스
A a1 = new A() {
@Override
public void abc() {
B b = new B();
b.bcd();
}
};
//2. 람다식으로 표현
A a2 = () -> {
B b = new B();
b.bcd();
};
//3. 정의된 인스턴스 메서드 참조
B b = new B();
A a3 = b::bcd;
a1.abc();
a2.abc();
a3.abc();
}
}
[ 내부에 void abc(int k) 추상 메서드를 가진 함수형 인터페이스 A와 익명 내부 클래스 및 람다식으로 구현한 메서드의 내용이 같은 경우 ]
interface A {
void abc();
}
class B {
void bcd() {
System.out.println("메서드");
}
}
① 익명 내부 클래스 및 람다식으로 각각 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public void abc(int k) {
System.out.println(k);
}
};
↓
//람다식
A a = (k) -> {
System.out.println(k);
};
abc(...)메서드 = system.out.println(...)메서드
② 정의되어 있는 인스턴스 메서드 참조
A a = System.out::println;
//인터페이스 A의 추상 메서드인 abc()는 System.out.println()을 참조
//(abc()를 호출하면 대신 System.out.println()을 호출하라는 뜻이다.)
System.out 자체가 객체이므로 객체를 따로 생성할 필요가 없다.
③ 코드
interface A {
void abc(int k);
}
public class RefOfIntanceMethod_Type1_2 {
public static void main(String[] args) {
//1. 익명 내부 클래스
A a1 = new A() {
@Override
public void abc(int k) {
System.out.println(k);
}
};
//2. 람다식으로 표현
A a2 = (int k) -> {
System.out.println(k);
};
//3. 인스턴스 메서드 참조
A a3 = System.out::println;
a1.abc(3);
a2.abc(3);
a3.abc(3);
}
}
클래스명::정적 메서드명
정적 메서드는 객체 생성 없이 클래스명으로 바로 사용할 수 있기 때문에 객체의 생성 없이 클래스명을 바로 사용했다고 생각하면 됨
[ abc() 추상 메서드 1개를 갖는 함수형 인터페이스 A와 정적 메서드 bcd()를 갖고 있는 B 클래스가 정의된 경우 ]
interface A {
void abc();
}
class B {
static void bcd() {
System.out.println("메서드");
}
}
① 익명 내부 클래스 및 람다식으로 각각 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public void abc() {
B.bcd();
}
};
↓
//람다식
A a = () -> {
B.bcd();
};
abc( ) 메서드 = B.bcd( ) 메서드
② 정의되어 있는 정적 메서드 참조
A a = B::bcd;
//인터페이스 A의 객체를 생성할 때 구현해야 하는 abc() 메서드를 B.bcd()와 동일하게 하라는 의미.
//abc()를 호출하는 대신 B.bcd()를 호출하겠다는 뜻이다.
③ 코드
interface A {
void abc();
}
class B {
static void bcd() {
System.out.println("메서드");
}
}
public class RefOFStaticMethod {
public static void main(String[] args) {
// 1. 익명 내부 클래스
A a1 = new A() {
@Override
public void abc() {
B.bcd();
}
};
// 2. 람다식으로 표현
A a2 = () -> {B.bcd();};
// 3. 정적 메서드 참조
A a3 = B::bcd;
a1.abc();
a2.abc();
a3.abc();
}
}
클래스명::인스턴스 메서드명
객체가 첫 번째 매개변수로 전달되므로 따로 생성할 필요는 없다.
[ abc(B b, int k)와 같이 2개의 매개변수를 입력받는 추상 메서드를 포함한 인터페이스 A와 bcd(int k)와 같이 하나의 매개변수를 갖는 인스턴스 메서드를 포함한 클래스 B가 정의된 경우 ]
interface A {
void abc(B b, int k);
}
class B {
void bcd(int k) {
System.out.println(k);
}
}
① 익명 내부 클래스 및 람다식으로 각각 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public void abc(B b, int k) {
b.bcd(k);
}
};
↓
//람다식
A a = (b, k) -> {
b.bcd();
};
abc(B b, int k) 메서드 = b.bcd(k) 메서드
② 첫 번째 매개변수로 전달된 객체의 메서드를 참조
A a = B::bcd;
③ 코드
interface A {
void abc(B b, int k);
}
class B {
void bcd(int k) {
System.out.println(k);
}
}
public class RefOfInstanceMethod_Type2_1 {
public static void main(String[] args) {
// 익명 내부 클래스
A a1 = new A() {
@Override
public void abc(B b, int k) {
b.bcd(k);
}
};
// 람다식
A a2 = (B b, int k) -> {b.bcd(k);};
// 직접 정의한 인스턴스 메서드 참조
A a3 = B::bcd;
a1.abc(new B(), 3);
a1.abc(new B(), 3);
a1.abc(new B(), 3);
클래스명::인스턴스 메서드명
[ 내부에 int abc(String str) 추상 메서드를 포함하는 인터페이스 A가 정의된 경우 ]
① 익명 내부 클래스 및 람다식으로 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public int abc(String str) {
return str.length();
}
};
↓
//람다식
A a = (str) -> str.length();
② 자바가 제공하는 인스턴스 메서드 참조
구현된 메서드의 내부에서는 입력매개변수로 넘어온 String 객체
의 length() 메서드
를 호출하고 있음
즉, 추상 메서드 abc()
는 String 클래스
의 length() 메서드
와 동일한 기능을 함
A a = String::length;
객체를 메서드 내부에서 생성한 것이 아니라 매개변수로 넘겨 주기 때문에 abc(String str) 추상 메서드의 매개변수는 length() 메서드 매개변수에 1개가 추가된 형태
③ 코드
interface A {
int abc(String str);
}
public class RefOfInstanceMethod_Type2_2 {
public static void main(String[] args) {
// 1. 익명 이너 클래스
A a1 = new A() {
@Override
public int abc(String str) {
return str.length();
}
};
// 2. 람다식
A a2 = (String str) -> str.length();
// 3. 자바가 제공하는 인스턴스 메서드 참조
A a3 = String::length;
System.out.println(a1.abc("안녕"));
System.out.println(a1.abc("안녕"));
System.out.println(a1.abc("안녕"));
}
}
배열 타입::new
인터페이스에 포함된 추상 메서드의 구현 메서드가 new 자료형[ ]과 같이 배열 객체의 생성 기능만을 수행할 때 사용
[정수 1개를 입력매개변수로 갖고, int[ ] 배열 타입을 리턴하는 추상메서드를 포함하는 인터페이스 A가 정의된 경우]
interface A {
int[] abc(int len);
}
① 익명 내부 클래스 및 람다식으로 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public int[] abc(int len) {
return new int[len];
}
};
↓
//람다식
A a = (len) -> new int[len];
abc(int len) 메서드 = new int[len]
② 배열의 new 생성자를 참조할 때
A a = int[]::new;
③ 코드
interface A {
int[] abc(int len);
}
public class RefOfArrayConstructor {
public static void main(String[] args) {
// 1. 익명 이너 클래스
A a1 = new A() {
@Override
public int[] abc(int len) {
return new int[len];
}
};
// 2. 람다식
A a2 = (int len) -> {return new int[len];};
// 3. 배열의 생성자 참조
A a3 = int[]::new;
int[] array1 = a1.abc(3);
System.out.println(array1.length);
int[] array2 = a2.abc(3);
System.out.println(array2.length);
int[] array3 = a3.abc(3);
System.out.println(array3.length);
}
}
클래스명::new
인터페이스의 추상 메서드가 클래스 타입의 객체를 리턴할 때 사용
[ 인터페이스 A는 B타입을 리턴하는 추상 메서드 abc( )를 갖고 있고, 클래스 B에는 기본 생성자를 포함해 2개의 생성자가 정의된 경우 ]
interface A {
B abc();
}
class B {
B() {} //첫 번째 생성자
B(int k) {} //두 번째 생성자
}
① 익명 내부 클래스 및 람다식으로 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public B abc() {
return new B();
}
};
↓
//람다식
A a = () -> new B();
② 클래스의 new 생성자를 참조할 때
A a = B::new;
//a.abc() 메서드를 호출하면 new B()를 실행해 객체를 생성하라는 의미.
③ 코드 - 기본 생성자 참조
interface A {
B abc();
}
class B {
B() {
System.out.println("첫 번째 생성자");
}
B(int k) {
System.out.println("두 번째 생성자");
}
}
public class RefOfClassConstructor_1 {
public static void main(String[] args) {
// 1. 익명 이너 클래스
A a1 = new A() {
@Override
public B abc() {
return new B();
}
};
// 2. 람다식
A a2 = () -> new B();
// 3. 클래스 생성자 참조
A a3 = B::new;
a1.abc();
a2.abc();
a3.abc();
}
}
[ 인터페이스 A는 B타입을 리턴하며, int k를 매개변수로 가진 추상 메서드 abc( )를 갖고 있고, 클래스 B에는 기본 생성자를 포함해 2개의 생성자가 정의된 경우 ]
① 익명 내부 클래스 및 람다식으로 A 인터페이스 객체 생성
//익명 내부 클래스
A a = new A() {
public B abc(int k) {
return new B(k);
}
};
↓
//람다식
A a = (k) -> new B(k);
abc(int k) 메서드 = new B(k)
② 클래스의 new 생성자를 참조할 때
A a = B::new;
//abc(k)를 호출하면 new B(k)를 실행하라는 의미.
④ 코드
interface A {
B abc(int k);
}
class B {
B() {
System.out.println("첫 번째 생성자");
}
B(int k) {
System.out.println("두 번째 생성자");
}
}
public class RefOfClassConstructor_2 {
public static void main(String[] args) {
// 1. 익명 이너 클래스
A a1 = new A() {
@Override
public B abc(int k) {
return new B(3);
}
};
// 2. 람다식
A a2 = (int k) -> {new B(3);};
// 3. 클래스 생성자 참조
A a3 = B::new;
a1.abc(3);
a1.abc(3);
a1.abc(3);
}
}
<Do it! 자바 완전 정복>
<Do it! 자바 프로그래밍 입문>
<이재환의 자바 프로그래밍 입문>
https://devlsy.tistory.com/entry/Java-Java-Lambda-%EC%A0%95%EB%A6%AC
https://zrr.kr/dZCw
https://diaryofgreen.tistory.com/137
https://zrr.kr/2C12
https://kellis.tistory.com/125
https://dev-kani.tistory.com/38
https://haeng-on.tistory.com/41
https://peterdrinker.tistory.com/383