람다식(Lambda Expression)
람다식은 함수형 프로그래밍 기법을 지원하는 자바의 문법 요소이다. 람다식은 간단히 말해서 메서드를 하나의 식(expression)으로 표현한 것으로 코드를 매우 간결하면서 명확하게 표현할 수 있다는 큰 장점을 가지고 있다.
다음의 예를 살펴보자.
//기존 메서드 표현 방식
void sayhello() {
System.out.println("HELLO!")
}
//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")
람다식에서는 기본적으로 반환 타입과 이름을 생략할 수 있다. 그러한 이유때문에 종종 이름없는 함수, 익명함수(anonymous) 라 부르기도 한다.
아래의 예시를 통해 메서드를 람다식으로 만드는 방법에 대해 살펴보자.
int sum(int num1, int num2) {
return num1 + num2;
}
sum 메서드를 람다식으로 바꿔보자
(int num1, int num2) -> { // 반환타입과 메서드명 제거 + 화살표 추가
return num1 + num2;
}
반환타입과 메서드명을 제거하고 코드블록 사이에 화살표를 추가해주었다.
// 기존 방식
void example1() {
System.out.println(5);
}
// 람다식
() -> {System.out.println(5);}
// 기존 방식
int example2() {
return 10;
}
// 람다식
() -> {return 10;}
// 기존 방식
void example3(String str) {
System.out.println(str);
}
// 람다식
(String str) -> { System.out.println(str);}
이렇게 람다식을 사용하면 기존 방식을 더 간편하고 명확하게 표현할 수 있다. 이외에도 특정 조건이 충족되면 람다식을 더욱 축약하여 표현할 수 있다.
// 기존 방식
int sum(int num1, int num2) {
return num1 + num2;
}
// 람다식
(int num1, int num2) -> {
num1 + num2
}
메서드 바디에 문장이 실행문이 하나만 존재할 경우 중괄호와 return 문을 생략할 수 있다.
(int num1, int num2) -> num1 + num2
거기에 매개변수 타입을 함수형 인터페이스를 통해 유추할 수 있는 경우에는 매개변수 타입도 생략할 수 있다.
(num1, num2) -> num1 + num2
함수형 인터페이스
람다식 또한 사실은 객체이다. 이름이 없는 익명 객체라 할 수 있다. 아래의 예제를 봐보자.
// sum 메서드 람다식
(num1, num2) -> num1 + num2
// 람다식을 객체로 표현
new Object() {
int sum(int num1, int num2) {
return num1 + num1;
}
}
위의 람다식으로 표현한 sum 메서드는 사실 아래와 같은 이름이 없는 익명 객체이다.
익명 객체는 익명 클래스를 통해 만들 수 있는데, 익명 클래스란 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 단 한번만 사용되는 일회용 클래스이다. 아래와 같이 생성과 선언을 한번에 할 수 있다.
new Object() {
int sum(int num1, int num2) {
return num1 + num1;
}
}
만약에 람다식이 객체라 한다면 객체에 접근하고 사용하기 위한 참조변수가 필요할 것이다. 그런데 기존에 객체를 생성할 때 만들었던 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
이 같은 문제를 해결하기 위해 사용하는 자바의 문법요소가 바로 함수형 인터페이스(Functional Interface)라고 할 수 있다. 즉 자바에서 함수형 프로그래밍을 하기 위한 새로운 문법 요소를 도입하는 대신, 기존의 인터페이스 문법을 활용하여 람다식을 다루는 것이라고 할 수 있다. 함수형 인터페이스는 단 하나의 추상 메서드만 선언될 수 있는데 이는 람다식과 인터페이스의 메서드가 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
위의 예제에서 함수형 인터페이스인 ExampleFunction에 추상메서드 sum()이 정의되어 있다. 이 함수형 인터페이스는 람다식은 참조할 참조변수를 선언할 때 타입으로 사용하기 위해 필요하다.
함수형 인터페이스 타입으로 선언된 참조변수 exampleFunction에 람다식이 할당되었으며 이제 exampleFunction을 통해 sum()메서드를 호출할 수 있다.
매개변수와 리턴값이 없는 람다식
@FunctionalInterface
public interface MyFunctionalInterface {
void accept();
}
이 인터페이스를 타깃 타입으로 갖는 람다식은 아래와 같은 형태로 작성되어야 한다. 람다식에서 매개변수가 없는 이유는 accept()가 매개변수를 가지고 있지 않기 때문이다.
MyFunctionalInterface example = () -> { ... };
// example.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);
}
이 인터페이스를 타깃 타입으로 갖는 람다식은 다음과 같은 형태로 작성해야 한다. 람다식에서 매개변수가 한 개인 이유는 추상메서드 accept()가 매개변수를 하나만 가지기 때문이다.
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()를 호출할 수 있다. 위의 예시와 같이 매개값으로 2를 주면 람다식의 x 변수에 2가 대입되고, x는 중괄호 {}에서 사용된다.
리턴값이 있는 람다식
@FunctionalInterface
public interface MyFunctionalInterface {
int accept(int x, int y);
}
이 인터페이스를 타깃 타입으로 갖는 람다식은 다음과 같은 형태로 작성해야 한다. 람다식에서 매개변수가 두 개인 이유는 accept()가 매개변수를 두 개 가지기 때문이다. 또한 accept()가 리턴 타입이 있기 때문에 중괄호 {}에는 return 문이 있어야 한다.
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(result4);
}
public static int sum(int x, int y){
return x + y;
}
}
//출력값
7
7
7
7
람다식이 대입된 인터페이스 참조변수는 다음과 같이 accept()를 호출할 수 있다. 매개값으로 2와 5를 주면 람다식의 x 변수에 2, y 변수에 5가 대입되고 x와 y는 중괄호에서 사용된다.
자바에서 기본적으로 제공하는 함수형 인터페이스
자바에서는 빈번하게 사용되는 함수형 인터페이스를 기본적으로 제공하고 있다.
메서드 레퍼런스
메서드 참조는 람다식에서 불필요한 매개변수를 제거할 때 주로 사용한다.
(left, right) -> Math.max(left, right)
람다식은 단순히 두 개의 값을 Math.max()메서드의 매개값으로 전달하는 역할만 하므로 다소 불편해 보인다. 또한 이 경우 입력값과 출력값의 반환타입을 쉽게 유추할 수 있으므로 입력값과 출력값을 일일이 적어주는게 크게 중요하지 않다. 이럴때는 다음과 같이 메서드 참조를 이용하면 깔끔하게 처리할 수 있다.
// 클래스이름::메서드이름
Math :: max // 메서드 참조
메서드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성되므로 인터페이스의 추상 메서드가 어떻게 매개변수를 가지고, 리턴타입이 무앗인가에 따라 달라진다.
IntBinaryOperator 인터페이스는 두 개의 int 매개값을 받아 int 값을 리턴하므로 Math::max 메서드 참조를 대입할 수 있다.
IntBinaryOperator operato = Math :: max; //메서드 참조
정적 메서드와 인스턴스 메서드 참조
정적 메서드를 참조할 때는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메서드 이름을 기술하면 된다.
인스턴스 메서드의 경우에는 먼저 객체를 생성한 다음 참조변수 뒤에 :: 기호를 붙이고 인스턴스 메서드 이름을 기술하면 된다.
아래 예제는 Calculator의 정적 및 인스턴스 메서드를 참조한다.
//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 연산자를 기술하면 된다.
생성자가 오버로딩되어 여러개가 있으면 컴파일러는 함수형 인터페이스의 추상 메서드와 동일한 매개변수 타입과 개수가 있는 생성자를 찾아 실행한다. 다음 예제를 확인해보자.
//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;
}
}
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) 실행
*/
위의 코드는 생성자 참조를 이용하여 두 가지 방법으로 Member 객체를 생성하고 있다. 하나는 Function<String, Member> 함수형 인터페이스의 Member apply(String) 메서드를 이용해서 Member 객체를 생성하고 다른 하나는 BiFunction<String, String, Member> 함수형 인터페이스의 Member 객체를 생성한다.
'공부 > 전자컴퓨터공학' 카테고리의 다른 글
Java Optional Class 란? 자바 기초 배우기 (0) | 2024.05.30 |
---|---|
Java 스트림(Stream) 이란? 자바 기초 배우기 (0) | 2024.05.30 |
Java 애너테이션(Annotation) 이란? 자바 기초 배우기 (0) | 2024.05.30 |
Java 컬렉션 프레임워크란? 자바 기초 배우기 (0) | 2024.05.30 |
Java 예외처리(Exception Handling)란? 자바 기초 배우기 (1) | 2024.05.28 |