티스토리 뷰

728x90

학습할 것 (필수)

  • 람다식 사용법
  • 함수형 인터페이스
  • Variable Capture
  • 메소드, 생성자 레퍼런스

람다(lambda) 란?

JDK8부터 추가된 자바 람다식이란 메서드를 하나의 `식(expresssion)`으로 표현한 것이다.

람다식은 함수를 간략하면서도 명확한 식으로 표현할 수 있게 해준다.

int[] arr = new int[5];
Arrays.setAll(arr, () -> (int)(Math.random() * 5) + 1);

위의 문장에서 () -> (int)(Math.random() * 5) +1 ) 이 람다식이다.

오직 람다식 자체만으로도 메서드의 역할을 대신할 수 있다.

메서드의 매개변수로 전달되어지는 것이 가능하고, 메서드의 결과로 반환될 수도 있다.

람다 표현식을 잘 알게되면 코드를 읽기 쉽고 이 기술을 기반으로 다른 혁신적인 기술인 함수형 프로그래밍과 스트림 처리가 가능해진다.

람다 표현식이 필요한 이유

람다식이 필요한 이유는 프레임워크와 라이브러리가 추가되면서 유연성과 다양성을 확보하기 위해 인터페이스 기반으로 개발을 많이 하는데, 이 때문에 프로그램의 명세 (인터페이스)와 구현(클래스)을 분리하고 결과적으로 많은 클래스 파일이 생긴다.

이에 대응하기 위해서 별도의 중첩 클래스나 익명 클래스 형태로 실제 내용을 구현하는데 이렇게 구현하게 되면 비즈니스 로직 구현보다 그것을 담기 위한 코드를 더 많이 작성하게되고 중복도 많아 지고 가독성도 현저히 떨어진다.

자바에서는 이런 비효율성을 막기위해서 람다식을 도입했다.

람다의 장점

  • 이름 없는 함수를 선언할 수 있다. 메서드에는 이름과 특정 클래스나 인터페이스에 포함되어야 하지만 람다 표현식은 이런 제약에서 벗어날 수 있다.
  • 반복적으로 작업해야 하는 기존의 비효율적인 코드 작성 방식이 필요 없다.
  • 코드를 파라미터로 전달할 수 있다. 외부에서 동작을 정의해서 메서드에 전달할 때 편리하게 사용할 수 있다.

람다 표현식 이해

코드를 통해서 람다 표현식을 이해

Thread 클래스의 Runnable 인터페이스를 구현할 때 이렇게 JDK8 전에는 Thread 클래스 생성자에 Runnable 인터페이스를 new 연산자로 직접 구현해줘야 했다.

public class LambdaExample {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
    }
}

위의 소스 코드는 익명 클래스를 이용해서 내용을 구현하는 전형적인 방법이다.

인터페이스를 생성자의 파라미터로 받거나 메서드의 파라미터로 받아서 처리할 때 유용하게 사용할 수 있다.

이것을 람다로 바꾸면 아래와 같이 바꿀 수 있다.

Thread thread = new Thread(() -> System.out.println("Hello Lambda!"));

람다 표현식 전환

1. 익명 클래스 선언 부분 제거

람다 표현식에서는 이름을 제거할 수 있다. 람다식으로 바꾸기전의 new Runnable에서 이름을 제거할 수 있다.

Thread 생성자의 인수는 Runnable 인터페이스가 유일하며, 입력해야 할 것이 Runnable 인터페이스 혹은 이를 구현한 클래스로 한정되기 때문에 인터페이스명을 선언할 필요가 없다.

public class LambdaExample {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Lambda!");
            }
        });
    }
}

2. 메서드 선언 부분 제거

그 다음으로 메서드명과 리턴 타입을 제거한다. 리턴이 없으면 void 이고 리턴이 있으면 타입은 이미 정해져있기 때문에 생략이 가능하다.

람다표현식에서는 리턴 타입이 생략되더라도 자바 컴파일러가 데이터 타입 추론을 통해 자동으로 결정해준다.

public class LambdaExample {
    public static void main(String[] args) {
        Thread thread = new Thread(
            @Override
            public void run() {
                System.out.println("Hello Lambda!");
            }
        );
    }
}

3. 람다 문법으로 정리

람다의 파라미터 목록을 메서드의 본문으로 전달하는 의미로 '->' 기호를 사용한다.

입력 파라미터가 없더라도 람다 표현식에서는 파라미터 목록을 남겨놔야 한다.

public class LambdaExample {
    public static void main(String[] args) {
        Thread thread = new Thread(
            () {
                System.out.println("Hello Lambda!");
            }
        );
    }
}

4. 최종 결과

public class LambdaExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("Hello Lambda!"));
    }
}

익명 클래스 기반으로 작성된 코드를 람다식으로 변경하는 절차 정리

  1. 익명 클래스를 이용해서 메서드 정의
  2. 익명 클래스 생성하기 위해 선언한 인터페이스 이름을 제거
  3. 메서드 파라미터 목록과 구현한 바디 영역을 제외하고 리턴 타입, 메서드 명을 삭제
  4. 람다 문법에 맞게 '->' 를 이용해서 문장 완성

람다 표현식 작성 방법

// 메소드
반환타입 메소드명 (매개변수 선언) {
    문장
}

// 람다식
(매개변수 선언) -> {
    문장
}

람다를 어렵게 생각하지말자. 람다는 새로운 것이 아니다. 람다 표현식이 없던 자바7 까지도 익명 클래스 등을 이용해서 동일한 비즈니스 로직을 구현할 수 있었다. 람다식을 이용하면 코드의 양이 줄어들고 깔끔해진다.

함수형 인터페이스

그런데 인터페이스를 생성자의 파라미터로 받거나 메서드의 파라미터로 받을 때 구현해야할 인터페이스가 여러개라면 어떻게 될까?

람다 표현식은 이름이 없고 단지 파라미터와 리턴타입으로만 식별하는데 어떻게 자바 컴파일러가 알 수 있을까?

//인터페이스
public interface InterfaceMany {
    void test();

    void test2();
}

//클래스
public class LambdaExample {
    public static void main(String[] args) {
        LambdaExample lambdaExample = new LambdaExample();
        lambdaExample.parameterInterface(new InterfaceMany() {
            @Override
            public void test() {
                
            }

            @Override
            public void test2() {

            }
        });
    }

    public void parameterInterface(InterfaceMany interfaceMany) {
        
    }
}

결론부터 말하자면 람다 표현식을 쓸 수 있는 인터페이스는 오직 public 메서드 하나만 가지고 있는 인터페이스여야 한다.

자바 8에서는 이러한 인터페이스를 함수형 인터페이스라고 부르고 함수형 인터페이스에서 제공하는 단 하나의 메서드를 함수형 메서드라고 부른다.

자바에서는 함수형 인터페이스를 만들 때 사용할 어노테이션을 제공한다. @FunctionalInterface

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
    ...
}

위의 코드는 자바 8에서 일상적인 프로그래밍에서 많이 사용할 법한 패턴을 정리해서 함수형 인터페이스로 만들어 놓은 java.util.function 패키지에 있는 Consumer 인터페이스이다.

@FunctionalInterface 가 선언되어 있는 클래스에서 default 메서드나 static 메서드가 아닌 추상 메서드를 구현하려고 하면 컴파일 에러가 난다.  함수형 인터페이스에는 오직 하나의 추상 메소드만 존재 해야 한다. 그래야만, 람다식과 인터페이스의 메소드가 1:1로 연결될 수 있다. static 메소드와 default 메소드의 개수에는 제약이 없다.

Variable Capture

메서드, 생성자 레퍼런스

람다 표현식을 자세히 살펴보면 -> 바디 왼쪽에 파라미터 목록은 불필요한, 그리고 형식적인 선언인 경우도 많다.

(String name) -> System.out.println(name) //람다 표현식

위의 내용을 잘 살펴보면 -> 왼쪽에 있는 파라미터는 우리가 이미 알고 있는 것이고, 실제 처리해야 하는 코드는 오른쪽에 있는 println 메서드이다. 이를 메서드 참조 형태로 바꾸면 아래와 같다.

System.out::println	//메서드 참조

println 메서드에는 당연히 출력해야 할 값이 파라미터로 전달되어야 하는데 이때 사용하는 값은 왼편에 생략된 (String name)을 이용하겠다는 것이 함축적으로 들어있다.

 

메서드 참조는 람다 표현식의 재사용성과 가독성을 높여주는 역할을 한다. 뿐만 아니라 람다 표현식이 아니더라도 기존에 작성한 메서드들을 람다 표현식이나 익명클래스를 대체해서 사용할 수 있다.

 

생성자 참조는 아래와 같은 문법으로 작성한다.

클래스명::new

람다 표현식과 메서드 참조는 주어진 객체의 메서드를 호출해서 변경된 결괏값을 리턴하는 구조다. 하지만 새로운 객체를 생성해서 리턴해야 하는 경우도 많은데 이런 경우에 생성자 참조를 유용하게 사용할 수 있다.

참조

자바의 정석 - www.yes24.com/Product/Goods/24259565?OzSrank=2

이것이 자바다  - www.yes24.com/Product/Goods/15651484

자바의 신 - www.yes24.com/Product/Goods/42643850

오라클 Docs -  docs.oracle.com/javase/tutorial/java/index.html

Practical 모던 자바 - www.yes24.com/Product/Goods/92529658

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함