랩터
[Java] 람다 본문
람다식(Lambda Expression)은 함수형 프로그래밍 기법을 지원하는 자바의 문법요소입니다.
람다식은 간단히 말해서 메서드를 하나의 ‘식(expression)’으로 표현한 것으로, 코드를 매우 간결하면서 명확하게 표현할 수 있다는 큰 장점이 있습니다.
학습 목표
- 람다식이 무엇이고, 어떻게 사용할 수 있는지 이해할 수 있다.
- 함수형 인터페이스를 통해 람다를 다루는 방법을 이해하고 설명할 수 있다.
- 람다식을 메서드 참조 방식으로 변환할 수 있다.
- 스트림(Stream)은 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다.
람다식의 기본 문법
//기존 메서드 표현 방식
void sayhello() {
System.out.println("HELLO!")
}
//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")
람다식에서는 기본적으로 반환타입과 이름을 생략할 수 있습니다.
// 기존 방식
int sum(int num1, int num2) {
return num1 + num2;
}
// 람다식
(int num1, int num2) -> {
num1 + num2
}
이 sum메서드는 내용을 좀더 축약할 수 있습니다.
먼저, 메서드 바디에 문장이 실행문이 하나만 존재할 때 우리는 중괄호와 return 문을 생략할 수 있습니다. 이 경우, 세미콜론까지 생략해야 합니다.
두 번째로, 매개변수 타입을 함수형 인터페이스를 통해 유추할 수 있는 경우에는 매개변수의 타입을 생략할 수 있습니다. 함수형 인터페이스에 대해서는 다음 콘텐츠에서 학습합니다.
(num1, num2) -> num1 + num2
함수형 인터페이스
자바에서 함수는 반드시 클래스 안에서 정의되어야 하므로 메서드가 독립적으로 있을 수 없고 반드시 클래스 객체를 먼저 생성한 후 생성한 객체로 메서드를 호출해야 합니다. 메서드와 동일시 여겼던 람다식 또한 사실은 객체입니다. 이름이 없기 때문에 익명 객체라 할 수 있습니다.
익명 객체는 익명 클래스를 통해 만들 수 있는데, 익명 클래스란 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 단 한번만 사용되는 일회용 클래스입니다.
new Object() {
int sum(int num1, int num2) { // 생성과 선언 한번에 가능
return num1 + num2;
}
}
기존에 객체를 생성할 때 만들었던 Object 클래스에는 sum이라는 메서드가 없으므로, Object 타입의 참조변수에 담는다고 하더라도 sum 메서드를 사용할 수 없습니다.
public class LamdaExample1 {
public static void main(String[] args) {
// 람다식 Object obj = (num1, num2) -> num1 + num2; 로 대체 가능
Object obj = new Object() {
int sum(int num1, int num2) {
return num1 + num1;
}
};
obj.sum(1, 2);
}
}
출력 결과
java: cannot find symbol
symbol: method sum(int,int)
location: variable obj of type java.lang.Object
이러한 문제를 해결하기 위해 사용하는게 바로 함수형 인터페이스라 할 수 있습니다.
함수형 인터페이스에는 단 하나의 추상 메서드만 선언될 수 있는데, 이는 람다식과 인터페이스의 메서드가 1:1로 매칭되어야 하기 때문입니다.
public class LamdaExample1 {
public static void main(String[] args) {
/* Object obj = new Object() {
int sum(int num1, int num2) {
return num1 + num1;
}
};
*/
ExampleFunction exampleFunction = (num1, num2) -> num1 + num2;
System.out.println(exampleFunction.sum(10,15));
}
@FunctionalInterface // 컴파일러가 인터페이스가 바르게 정의되었는지 확인하도록 합니다.
interface ExampleFunction {
int sum(int num1, int num2);
}
// 출력값
25
매개변수와 리턴값이 없는 람다식
다음과 같이 매개변수와 리턴값이 없는 추상 메서드를 가진 함수형 인터페이스가 있다고 가정해 보겠습니다.
@FunctionalInterface
public interface MyFunctionalInterface {
void accept();
}
람다식에서 매개변수가 없는 이유는 accept()가 매개변수를 가지지 않기 때문입니다. 람다식이 대입된 인터페이스의 참조 변수는 위의 주석과 같이 accept() 를 호출할 수 있습니다. accept() 의 호출은 람다식의 중괄호 {}를 실행시킵니다.
@FunctionalInterface
interface MyFunctionalInterface {
void accept();
}
public class MyFunctionalInterfaceExample {
public static void main(String[] args) throws Exception {
MyFunctionalInterface example = () -> System.out.println("accept() 호출");
example.accept();
}
}
// 출력값
accept() 호출
매개변수가 있는 람다식
다음과 같이 매개 변수가 있고 리턴값이 없는 추상 메서드를 가진 함수형 인터페이스가 있다고 가정해 보겠습니다.
@FunctionalInterface
public interface MyFunctionalInterface {
void accept(int x);
}
public class MyFunctionalInterfaceExample {
public static void main(String[] args) throws Exception {
MyFunctionalInterface example;
example = (x) -> {
int result = x * 5;
System.out.println(result);
};
example.accept(2);
example = (x) -> System.out.println(x * 5);
example.accept(2);
}
}
// 출력값
10
10
람다식에서 매개변수가 한 개인 이유는 추상메서드 accept()가 매개변수를 하나만 가지기 때문입니다.
리턴값이 있는 람다식
다음과 같이 매개 변수와 리턴값을 가지는 추상 메서드를 포함하는 함수형 인터페이스가 있습니다.
@FunctionalInterface
public interface MyFunctionalInterface {
int accept(int x, int y);
}
람다식에서 매개 변수가 두 개인 이유는 accept()가 매개변수를 두 개 가지기 때문입니다.
또한, accept()가 리턴 타입이 있기 때문에 중괄호 { }에는 return 문이 있어야 합니다.
package Lambda;
public class MyFunctionalInterfaceExample {
public static void main(String[] args) throws Exception{
MyFunctionalInterface example;
example = (x,y) -> {
int result = x + y;
return result;
};
int result1 = example.accept(2, 5);
System.out.println(result1);
example = (x, y) -> {return x + y;};
int result2 = example.accept(2, 5);
System.out.println(result2);
example = (x,y) -> x + y;
//return문만 있으면, 중괄호 {}와 return문 생략 가능
int result3 = example.accept(2, 5);
System.out.println(result3);
example = (x,y) -> sum(x,y);
//return문만 있으면, 중괄호 {}와 return문 생략 가능
int result4 = example.accept(2, 5);
System.out.println(result3);
}
public static int sum(int x, int y){
return x + y;
}
}
메서드 레퍼런스
메서드 참조는 람다식에서 불필요한 매개변수를 제거할 때 주로 사용합니다.
메서드 참조는 정적 혹은 인스턴스 메서드를 참조할 수 있고 생성자 참조도 가능합니다.
정적 메서드와 인스턴스 메서드 참조
정적 메서드를 참조할 때는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메서드 이름을 기술하면 됩니다.
//Calculator.java
public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}
public int instanceMethod(int x, int y) {
return x * y;
}
}
import java.util.function.IntBinaryOperator;
public class MethodReferences {
public static void main(String[] args) throws Exception {
IntBinaryOperator operator;
/*정적 메서드
클래스이름::메서드이름
*/
operator = Calculator::staticMethod;
System.out.println("정적메서드 결과 : " + operator.applyAsInt(3, 5));
/*인스턴스 메서드
인스턴스명::메서드명
*/
Calculator calculator = new Calculator();
operator = calculator::instanceMethod;
System.out.println("인스턴스 메서드 결과 : "+ operator.applyAsInt(3, 5));
}
}
/*
정적메서드 결과 : 8
인스턴스 메서드 결과 : 15
*/
생성자 참조
메서드 참조는 생성자 참조도 포함합니다.
생성자를 참조한다는 것은 객체 생성을 의미합니다
생성자 참조로 표현하면 다음과 같습니다. 클래스 이름 뒤에 ::기호를 붙이고 new 연산자를 기술하면 됩니다.
클래스 :: new
생성자가 오버로딩되어 여러 개가 있으면 컴파일러는 함수형 인터페이스의 추상 메서드와 동일한 매개 변수 타입과 개수가 있는 생성자를 찾아 실행합니다.
//Member.java
public class Member {
private String name;
private String id;
public Member() {
System.out.println("Member() 실행");
}
public Member(String id) {
System.out.println("Member(String id) 실행");
this.id = id;
}
public Member(String name, String id) {
System.out.println("Member(String name, String id) 실행");
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
package Lambda;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ConstructorRef {
public static void main(String[] args) throws Exception{
Function<String, Member> function1 = Member::new;
Member member1 = function1.apply("kimcoding");
BiFunction<String, String, Member> function2 = Member::new;
Member member2 = function2.apply("kimcoding", "김코딩");
}
}
/*
Member(String id) 실행
Member(String name, String id) 실행
*/
Function<String, Member> 함수형 인터페이스의 Member apply(String) 메서드를 이용해서 Member 객체를 생성하고, 다른 하나는 BiFunction<String, String, Member> 함수형 인터페이스의 Member 객체를 생성합니다.
'공부 > JAVA' 카테고리의 다른 글
[Java] 스트림 - 스트림의 생성 (0) | 2024.07.08 |
---|---|
[Java] 스트림 - 핵심 개념과 특징 (0) | 2024.07.08 |
[Java] 컬렉션 프레임워크 (Collection Framework) (0) | 2024.07.04 |
[Java] 예외 처리 (0) | 2024.07.03 |
[Java] 제네릭 (0) | 2024.07.03 |