0. 개요
- 자바에서 람다를 잘 써보고 싶은 마음에 본 포스팅을 준비했습니다.
- 우선 람다를 이해하기 위해서는 기본적인 인터페이스에 대한 이해도가 필요합니다.
- 인터페이스를 시작으로 람다에 대해 배워봅시다!
1. 인터페이스를 매개변수로 받는 메서드
- 인터페이스를 매개변수로 받으면 어떻게 될까?
- 한번도 의식적으로 사용해 본 적도, 생각해 본 적도, 고민해 본 적도 없는 질문이다.
- 다음 코드를 보자.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
}
public interface PrintThing {
void print();
}
- 위의 코드에서 main() 메서드에 있는 sound() 메서드는 PrintThing 인터페이스를 매개변수로 받는다.
- 그렇다면 저 자리에는 어떤 객체가 들어가야 하는 것일까?
- PrintThing 인터페이스를 implements 받는 클래스의 객체가 들어가면 된다!
- 신기하지 않은가?!?! 이걸 알게되고 나서 코드 작성에 있어 확장성이 한단계 업그레이드된 기분이 들었다.
- 모두가 알고 있었겠지만, 나는 몰랐다.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
Dog doggy = new Dog();
sound(doggy);
}
public interface PrintThing {
void print();
}
public class Dog implements PrintThing {
public String name;
public Dog(){};
@Override
public void print() { // 인터페이스 상속 메서드
System.out.println("BOW WOW");
}
}
2. 그래서 이게 람다랑 무슨 상관인데...?
- 라고 생각했다면 당신은 똑똑하다! 생각할 줄 아는 사람! 배운 사람!
- 자, 이제부터는 람다를 왜 사용하는지 그 본질에 대해서 알아보자.
- 위에서 아래와 같은 코드를 보았을 것이다.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
Dog doggy = new Dog();
sound(doggy);
}
public interface PrintThing {
void print();
}
public class Dog implements PrintThing {
public String name;
public Dog(){};
@Override
public void print() { // 인터페이스 상속 메서드
System.out.println("BOW WOW");
}
}
- 위의 로직의 흐름을 살펴보자.
- 우선 sound() 메서드를 사용하기 위해서는 PrintThing 인터페이스를 상속받은 클래스의 객체가 필요하다.
- 그러면 PrintThing 인터페이스를 상속받은 클래스를 만들어줘야 한다.
- 결국 Dog라는 클래스를 만들고 해당 클래스의 객체를 생성하여 사용하므로써 sound() 메서드를 사용할 수 있게 된다.
- 귀찮다. 이 과정 자체가 귀찮다.
- 만약 sound() 메서드를 사용하는 것이 지속적으로, 재사용성이 있는 메서드가 아니라 일회성으로 사용하는 것이라면?
- 그저 비효율의 끝이다.
그래서 람다를 쓴다.
- 이 귀찮음과 이 비효율적인 상황이 어쩌면 람다의 탄생 목적이지 않을까? 생각해본다.
- 즉, 람다는 메서드 그 자체를 매개변수로 넘기기 위해서 사용하는 것이다.
- 그렇다면 위에서 봤던 코드를 어떻게 람다로 바꿀 수 있는지 하나하나 차근차근 보자.
3. 람다로 교체
- 위에서 귀찮음을 강조했다.
- 인터페이스 상속받는 것도 귀찮고, 오버라이드 하는 것도 귀찮다고 말했다.
- sound() 메서드를 그냥 바로 쓸 수는 없을까?
- 아래의 코드처럼 말이다.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
sound(
public void print() {
System.out.println("BOW WOW");
}
);
}
public interface PrintThing {
void print();
}
- 놀라지 마시라. 위의 코드처럼 사용하려는 것이 람다의 컨셉 그 자체다.
- 그럼 위의 코드를 람다의 문법에 맞게 바꿔보자.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
sound(() -> {
System.out.println("BOW WOW");
}
);
}
public interface PrintThing {
void print();
}
- 람다는 접근지정자, 반환형, 메서드명을 필요로 하지 않는다. 자바가 알아서 확인하고 체크한다.
- 그러므로 저 3가지 요소를 제거하면 괄호만 남게된다.
- 여기에서 괄호와 중괄호 사이에 화살표만 넣어주면 람다식이 완성된다.
- 추가로 만약 람다 표현식으로 전달하려는 것이 단일 표현식이라면, 중괄호도 필요없다.
- 아래의 최종형태를 살펴보자.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
sound(() -> System.out.println("BOW WOW"));
}
public interface PrintThing {
void print();
}
4. 람다를 객체로 만들 수 있지 않을까?
- 위의 코드를 보면 람다식을 이용하여 메서드를 매개변수화 시켜서 넣었다.
- 그럼 람다식 자체를 객체화 시킬 수 있지 않을까?
- 그렇다. 아래의 코드를 살펴보자.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
PrintThing printThing = () -> System.out.println("BOW WOW");
sound(printThing);
}
public interface PrintThing {
void print();
}
- 지금까지는 매개변수가 없는 인터페이스의 메서드를 사용하여 구현해보았다.
- 그렇다면 만약 인터페이스의 추상메서드에 매개변수가 존재한다면 어떻게 해야할까?
5. 매개변수가 존재하는 추상메서드를 람다로 표현해보기
- 다음 예시코드를 보자.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print();
}
PrintThing printThing = () -> System.out.println("BOW WOW");
sound(printThing);
}
public interface PrintThing {
void print(String prefix);
}
- 위의 예시처럼 인터페이스 내부의 추상메서드에 매개변수가 할당 된다면 람다식을 어떻게 작성해야할까?
- 두려워할 것 없다! 다음과 같이 변경하면 그만이다!
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print("myDog: "); // 여기에 반드시 String 매개변수가 들어간다!
}
PrintThing printThing = (prefix) -> System.out.println(prefix + "BOW WOW");
sound(printThing);
}
public interface PrintThing {
void print(String prefix);
}
- 앞서 public void print()에서 접근지정자, 반환형, 메서드명을 제거하고 괄호만 남겨두었던 것을 기억할 것이다.
- 그렇다면 이번에는 public void print(String prefix)에서 동일하게 접근지정자, 반환형, 메서드명을 제거하고 괄호만 남긴다.
- 그러면 (String prefix)만 남게 된다.
- 이를 토대로 람다식을 작성하면 된다. 위의 예시코드처럼 말이다.
- 사실 위의 예시코드처럼 String 타입의 매개변수를 반드시 사용해야만 하는 것은 아니다.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print("myDog: ");
}
PrintThing printThing = (prefix) -> System.out.println(prefix + "BOW WOW");
sound(printThing);
}
public interface PrintThing {
void print(String prefix);
}
- 다만, 위의 코드처럼 람다식에 명시적으로 String 타입의 매개변수를 입력해야한다는 차이가 있을 뿐이다.
6. 매개변수가 한개 이상이라면?
- 매개변수가 한개 이상일 때에도 마찬가지다.
- 다음의 예시코드처럼 작성하면 된다.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print("myDog: ", "!!!");
}
PrintThing printThing = (prefix, suffix)
-> System.out.println(prefix + "BOW WOW" + suffix);
sound(printThing);
}
public interface PrintThing {
void print(String prefix, String suffix);
}
7. 반환형이 존재하는 경우
- 반환형이 존재한다면, 해당 반환형에 맞는 값을 return 해주면 된다.
- 다음 예시코드를 보자.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print("myDog: ", "!!!");
}
PrintThing printThing = (prefix, suffix) -> {
return prefix + "BOW WOW" + suffix;
};
sound(printThing);
}
public interface PrintThing {
String print(String prefix, String suffix);
}
- 한가지 더 편의기능이 존재한다.
- 반환형이 존재하면 자바에서 알아서 반환형이 있다는 것을 인식한다.
- 그러므로 굳이 위의 코드처럼 작성하는 것이 아니라 다음의 예시코드처럼 작성할 수 있다.
- 다만, 이 또한 하나의 표현식으로 작성하는 경우에만 해당한다.
예시코드
public static void main(String[] args) {
static void sound(PrintThing printThing) {
printThing.print("myDog: ", "!!!");
}
PrintThing printThing = (prefix, suffix) -> prefix + "BOW WOW" + suffix;
sound(printThing);
}
public interface PrintThing {
String print(String prefix, String suffix);
}
8. Functional Interface와 람다의 상관관계
- 아래의 예시코드를 보자.
예시코드
@FunctionalInterface
public interface PrintThing {
String print(String prefix, String suffix);
}
- 위의 예시코드처럼 인터페이스에 1개의 추상 메서드만 존재하는 구조를 우리는 Functional Interface라고 부른다.
- 존재하는 하나의 추상 메서드가 static일수도, default일수도 있다.
- 중요한 것은 인터페이스에 하나의 추상 메서드만이 존재하는 것이다.
- 그리고 Functional Interface의 경우 어노테이션을 사용해서 명시적으로 표시할 수 있다.
- 이는 다른 이름으로 SAM(Single Abstract Method)이라고 불리기도 한다.
9. Functional Interface와 람다의 상관관계
- 이번 포스팅의 핵심이기도 한 부분이다.
- 람다를 사용하고 싶다면 반드시 Functional Interface를 대상으로 해야한다는 것이다.
- 이에 대해서는 어쩌면 당연한 것일 수도 있다.
- 왜냐면 람다를 사용하는 경우 메서드의 접근지정자, 반환형, 메서드명을 명시하지 않기 때문이다.
- 이는 자바에서 알아서 람다식으로 표현된 해당 메서드를 찾아 사용한다는 것인데, 결국 인터페이스에 하나의 추상메서드만 존재했을 때 람다로 표현된 메서드가 무엇인지 자바가 인식할 수 있다는 이야기이기도 하다.
- 즉, 인터페이스에 추상 메서드가 2개라면 람다식으로 표현한 메서드가 어떤 추상 메서드를 가리키는 것인지 알 수 없다는 것이다.
- 이와 같은 이유로 람다를 함수형 표현식이라고 부르기도 하는 것이다.
10. Anonymous Inner Class
- 람다를 사용하기 위해서 반드시 Functional Interface를 정의하여 사용할 필요는 없다.
- Anonymous Inner Class를 사용하면 Functional Interface처럼 람다를 사용할 수 있기 때문이다.
- 이에 대한 내용은 추후에 포스팅으로 정리해보겠다.
- 여기까지 기본적인 람다의 개념에 대해서 알아보았다.
'Back-end > Java 개념' 카테고리의 다른 글
지극히 개인적인 OOP에 대한 원초적인 접근과 해석 (4) | 2023.08.06 |
---|---|
AOP(Aspect Oriented Programming) 개념과 구현 (0) | 2023.07.24 |
Optional이 뭔데? 왜 쓰는데? (0) | 2023.06.23 |
Unreachable Statement 오류 (0) | 2022.05.10 |
Collections와 Collection에 대하여 (0) | 2022.05.09 |
댓글