ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lambda Expression 1. 람다식
    Java 2020. 2. 8. 15:54
    반응형

    람다식(Lambda Expression)의 도입 (JDK 1.8 ~)

    람다식의 도입으로 인해, 자바는 객체지향언어인 동시에 함수형 언어가 됨

    람다식이란

    메서드를 하나의 식(expression)으로 표현한 것

    람다식으로 표현시 메서드의 이름과 반환값이 없어지므로 익명함수(Annoymous function)이라고도 함

    익명 메서드가 아닌 익명 함수라고 하는 이유!
    수학의 함수와 자바의 메서드는 사실 의미가 같지만, 자바의 메서드는 특정 클래스에 반드시 속해야만 한다는 제약이 있기 때문에 메서드(Method)라는 용어를 선택하여 구분하였었음
    람다식을 통하면 메서드의 역할인 람다식이 하나의 독립적인 기능을 하기 때문에 함수(Function)라는 용어를 사용하게 됨
    int[] arr = new int[3];
    Arrays.setAll(arr, (i) -> (int)(Math.random()*3)+1);
    
    int lambdaMethod() {
        return (int)(Math.random()*3() + 1;
    }

    장점

    간결하면서도 이해하기 쉬움

    기존 메서드의 단점 : 클래스에 포함되어야 하므로 클래스를 새로 생성해야 함, 객체도 생성해야만 해당 메서드를 호출할 수 있음

    람다식은 기존 메서드의 단점인 긴 과정 없이 오직 람다식만으로 메서드를 대신할 수 있음

    메서드의 매개변수로 전달되어지는 것이 가능하며, 메서드의 결과로 반환할 수도 있음

    즉, 메서드를 변수처럼 다루는 것이 가능해짐

    람다식 작성방법

    메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 body{} 사이에 "->"를 추가하면 됨

    // 메서드
    반환타입 메서드이름 (매개변수 선언) {
        내용
    }
    
    // 람다식
                   (매개변수 선언) -> {
        내용         
    }

    예제

    // 메서드
    int max(int a, int b) {
        return a > b ? a : b;
    }
    int max(int a, int b) { return a > b ? a : b; }
    
    // 람다식
           (int a, int b) -> {
        return a > b ? a : b;
    }
           (int a, int b) -> { return a > b ? a : b ; }
           
    /* 람다식 - 반환값이 있을 경우 return 대신 expression(식)으로 대체 가능
     * 문장(statement)이 아닌 식(expression)의 경우 끝에 ;를 붙이지 않음
     */
           (int a, int b) -> {
        a > b ? a : b      
    }
           (int a, int b) -> { a > b ? a : b }
    
    /* 람다식 - 매개변수 타입의 경우 추론이 가능한 경우엔 생략 할 수 있음 
     * 보통 대부분이 생략 가능함
     * 위처럼 반환 타입이 대신 식으로 하는 이유도 항상 추론이 가능하기 때문
     * 단, 타입을 쓸거면 다 쓰고 생략할거면 전체 생략해야 함, 부분 생략 허용하지 않음
     */
           (a, b) -> {
        a > b ? a : b
    }
           (a, b) -> a > b ? a : b
    /* 매개변수가 하나일 경우엔 괄호 생략 가능 */
    (a) -> a * a
    a   -> a * a
    
    /* 타입이 있는 경우 생략 불가 */
    (int a) -> a * a
    // int a -> a * a 
    
    /* {} 안의 문장이 하나일 때는 생략 가능, 단 ;는 제거해야함 */
    (String str, Int in) -> {
        System.out.println(str + "=" + in);
    }
    
    (String str, Int in) ->
        System.out.println(str + "=" + in);
        
    /* {} 안의 문장이 return인 경우 {} 생략 불가 */
    (int a, int b) -> { return a > b ? a : b; }
    (int a, int b) ->   return a > b ? a : b  // 불가
    /* 1 */
    int moreBig(int a, int b) {
        return a > b ? a : b;
    }
    
    (int a, int b) -> { return a > b ? a : b; }
    (int a, int b) -> { a > b ? a : b }
    (int a, int b) -> a > b ? a : b
    (a, b) -> a > b ? a : b
    
    /* 2 */
    void printMethod(String message) {
        System.out.println(message);
    }
    
    (String message) -> { System.out.println(message) }
    (String message) -> System.out.println(message)
    message -> System.out.println(message)
    
    /* 3 */
    int sArr(int[] arr) {
        int sum = 0;
        for (int i : arr) {
            sum += 1;
        }
        return sum;
    }
    
    (int[] arr) -> {
        int sum = 0;
        for (int i : arr)
            sum += 1;
        return sum;
    }
    
    /* 4 */
    Collections.sort(list, new Comparator<String>() {
        public int compare(String s1, String s2) {
            return s2.compareTo(s1);
        }
    }
    
    Collections.sort(list, (s1, s2) -> s2.compareTo(s1));

    함수형 인터페이스(Functional Interface)

    람다식과 익명 클래스의 객체

    람다식은 익명클래스의 객체와 동등함

    // 익명클래스
    new Object() {
        int moreBig(int a, int b) {
            return a > b ? a : b;
        }
    }
    
    // 람다식
    (a, b) -> a > b ? a : b

    람다식으로 정의된 익명 객체의 메서드를 호출하는 방법

    타입 참조변수명 = (int a, int b) -> a > b ? a : b;

    타입으로 지정할 수 있는 것 : (참조형이기 때문에) 클래스 또는 인터페이스 중 람다식과 동등한 메서드가 정의되어 있는 것 => 그래야 참조변수로 람다식(혹은 참조변수)의 메서드를 호출할 수 있음

    interface MyFunction {
        public abstract int moreBig(int a, int b);
    }
    
    MyFunction imsi = new MyFunction() {
                         public int moreBig(int a, int b) {
                             return a > b ? a : b;
                         }
                   }
    int big = imsi.moreBig(2, 3); // 익명 객체 메서드 호출
    
    MyFunction imsi2 = (int a, int b) -> a > b ? a : b; // 익명 객체를 람다식으로 대체
    int big2 = imsi2.moreBig(2, 3); // 익명 객체 메서드 호출

    함수형 인터페이스 정의

    하나의 메서드가 선언된 인터페이스를 정의하여 람다식을 다루는 것은 기존의 자바 규칙을 어기지 않으면서 자연스러움

    => 람다식을 인터페이스를 통해 다루기로 결정

    => 람다식을 다루기 위한 인터페이스 = 함수형 인터페이스

    @FunctionalInterface // 붙이면 컴파일러가 함수형 인터페이스를 올바르게 정의했는지 확인하는 애노테이션
    interface MyFunction {  // 함수형 인터페이스인 MyFunction을 정의
        public abstract int moreBig(int a, int b);
    }

    제약) 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 함

    => 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 위해

    static 메서드나 default 메서드의 경우엔 제약이 없음

    함수형 인터페이스 타입의 매개변수와 반환타입

    람다식을 참조 변수로 다룰 수 있다는 것 = 메서드를 통해 람다식을 주고받을 수 있다는 것

    => 코드가 더 간결하고 이해가 쉬워짐

    // 매개변수가 MyFunction타입이면, 메서드 호출시 람다식 참조 참조변수를 매개변수로 지정해야 함
    @FunctionalInterface
    interface MyFunction {
        void myMethod();    // 추상 메서드
    }
    
    // 매개 변수가 함수형 인터페이스인 메서드
    void aMethod(MyFunction method1) { // 매개변수의 타입이 함수형 인터페이스임
        method1.myMethod();            // MyFunction에 정의된 메서드를 호출
    }
        ...
    MyFunction mf = () -> System.out.println("myMethod()");
    aMethod(mf);
    
    // 참조변수를 지정하지 않고 직접 람다식으로 매개변수를 지정한 메서드도 가능함
    aMethod(() -> System.out.println("myMethod()"); // 람다식을 매개변수로 지정
    
    // 반환 타입이 함수형 인터페이스인 메서드
    MyFunction myMethod() {
        MyFunction mf = () -> {};
        return mf;
    }

    위의 예를 모조리 사용한 예시

    class Lambda1 {
        static void execute(MyFunction mf) { // 매개변수의 타입이 MyFunction인 메서드
            mf.run();
        }
    
        static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드
            MyFunction mf = () -> System.out.println("mf3.run()");
            return mf;
        }
    
        public static void main(String[] args) {
            // 람다식으로 myFunction의 run() 구현
            MyFunction mf1 = () -> System.out.println("mf1.run()");
    
            MyFunction mf2 = new MyFunction() { // 익명클래스로 run()을 구현
                public void run() {            // public을 반드시 붙여야 함
                    System.out.println("mf2.run()");
                }
            };
    
            MyFunction mf3 = getMyFunction();
    
            mf1.run();
            mf2.run();
            mf3.run();
    
            execute(mf1);
            execute( () -> System.out.println("run()") );
        }
    }
    
    /*
    Connected to the target VM, address: '127.0.0.1:50726', transport: 'socket'
    f1.run()
    f2.run()
    f3.run()
    f1.run()
    run()
    Disconnected from the target VM, address: '127.0.0.1:50726', transport: 'socket'
    
    Process finished with exit code 0
     */

    람다식의 타입 & 형변환

    함수형 인터페이스로 람다식을 참조할 수 있지만, 람다식의 타입과 함수형 인터페이스 타입은 항상 일치하진 않음

    = 람다식은 익명 객체이고, 익명 객체는 타입을 컴파일러가 임의로 이름을 정하기 때문에 알 수 없음

    => 그렇기 때문에 대입 연산자 사용시 양 변의 타입을 일치시키기 위해 형변환이 필요함 (But. 생략 가능)

    @FunctionalInterface
    interface MyFunction {
        void method();
    }
    
    // 형변환
    MyFunction mf = (MyFunction)(() -> {});
    
    // 생략 가능함
    MyFunction mf = (() -> {});
    
    // 단, Object로의 형변환은 불가
    Object obj = (Object)(() -> {});
    
    // Object로 굳이 형변환을 하고 싶다면?
    Object obj = (Object)(MyFunction)(() -> {});
    String str = ((Object)(MyFunction)(() -> {})).toString();

    Object로는 바로 형변환이 안됨

    = 함수형 인터페이스로 변환 후 Object로 다시 변환해야 함

    => 아마 이유?(toString 해보면 나옴) 익명 객체 타입 : '외부클래스명$번호' / 람다식 타입 : '외부클래스명$Lambda$번호'

    외부 변수를 참조하는 람다식

    람다식도 익명 클래스의 인스턴스(익명 객체)이므로 람다식에서 외부에 선언된 변수에 접근하는 규칙은 익명 클래스와 동일

    람다식(또는 익명 객체)에서 참조하는 지역변수는 final이 붙지 않더라도 상주로 간주

    외부 지역변수와 같은 이름의 람다식 매개변수는 허용되지 않음

    class Outer {
        int val = 10; // Outer.this.val
    
        class Inner {
            int val = 20; // this.val
    
            void method(int i) {  // void method(final int i)
                int val = 30;     // final int val = 30;
    //          i = 10;           // 에러) 상수의 값 변경 불가
    
                MyFunction mf = () -> {
                    System.out.println("             i : " + i);
                    System.out.println("           val : " + val);
                    System.out.println("      this.val : " + ++this.val);
                    System.out.println("Outer.this.val : " + ++Outer.this.val);
                };
    
                mf.run();
            }
        } // Inner Class 끝
    }     // Outer Class 끝
    
    class Lambda1 {
        public static void main(String[] args) {
            Outer outer = new Outer();
            Outer.Inner inner = outer.new Inner();
            inner.method(100);
        }
    }
    
    /*
                 i : 100
               val : 30
          this.val : 21
    Outer.this.val : 11
    
    Process finished with exit code 0
     */
    반응형

    'Java' 카테고리의 다른 글

    Stream(스트림) - 1. 스트림이란  (0) 2020.02.13
    Lambda Expression 2. Function 패키지  (0) 2020.02.08
    Enum(열거형)  (0) 2020.02.08
    서버 통신 - Socket / UDP  (0) 2020.02.07
    Serializable  (0) 2020.02.07

    댓글

Designed by Tistory.