본문 바로가기

Back-End

[이것이자바다] 14장 람다식

14장 람다식

1절 람다식이란?

  • 함수적 프로그래밍
    • y = f(x) 형태의 함수로 구성된 프로그래밍 기법
      • 객체 지향보다 효율적인 경우
        • 대용량 데이터 처리시에 유리
          • 데이터를 객체 생성 후 처리 보다는 데이터를 바로 처리하는 것이 유리
          • 멀티코어에서 병렬 처리 및 취합시 객체보다는 함수가 유리
  • 자바 8부터 함수적 프로그래밍 지원
    • 람다식을 언어차원에서 지원함
      • 람다 계산법에서 사용된 식을 프로그래밍 언어에 접목
      • 익명함수를 생성하기 위한 식
    • 자바에서 람다식을 수용한 이유
      • 코드가 매우 간결해진다.
      • 대용량 데이터를 필터링 또는 매핑에서 쉽게 집계 가능
  • 자바에서 람다식을 함수적 인터페이스의 익명 구현 객체로 취급 한다.
  • 함수적 인터페이스는 하나의 메서드만 가지고 있는 인터페이스

람다식 -> 매개변수를 가진 코드 블록 -> 익명구현객체
Runnable 확인

2절 람다식 기본 문법

  • 기본적으로 함수적 스타일의 람다식을 작성하는 방법
    (타입 매개변수, ...) -> {실행문}
  • 매개타입은 런타임시에 대입값에 따라 자동으로 인식하기 떄문에 생략 가능
    (a) -> {System.out.println(a);}
  • 하나의 매개변수만 있을 경우 ()괄호 생략 가능
    a -> {System.out.println(a);}
  • 하나의 실행문만 있다면 중괄호 {} 생략 가능
    a -> System.out.println(a);
  • 매개변수가 없다면 괄호를 생략할 수 없음
    () -> {실행문;...};
  • 리턴값이 있는 경우, return 문을 사용
    (x,y) -> {return x+y;};
  • 중괄호{} 에 return 문만 있는 경우, 중괄호를 생략 가능
    (x,y) -> x+y

3절 타겟 타입과 함수적 인터페이스

  • 타겟 타입(target type)
    • 람다식이 대입괴는 인터페이스를 말한다.
    • 익명 구현 객체를 만들 때 사용할 인터페이스다.
//인터페이스를 타겟 타입이라 한다.
인터페이스 변수 = 람다식;
  • 함수적 인터페이스(functional interface)
    • 모든 인터페이스는 람다식의 타겟 타입이 될 수 없다.
      • 람다식은 하나의 메서드를 정의 해야힌다.
      • 하나의 추상 메서드만 선언된 인터페이스만 타겟 타입이 될 수 있다.
    • 함수적 인터페이스
      • 하나의 추상 메서드만 선언된 인터페이스를 말한다.
    • @FunctionalInterface 어노테이션
      • 하나의 추상 메서드만을 가지는지 컴파일러가 체크한다.
      • 두개이상의 추상 메서드가 선언되어 있으면 컴파일 오류 발생
      • 필수는 아니지만 위에서 말한것과 같이 두개 이상의 메서드를 선언하면 함수적인터페이스 사용이 불가하다.

매개변수와 리턴값이 없는 람다식

public class MyfunctionalExample {
    public static void main(String[] args){
        MyFunctionalInterface mfi;

        //MyFunctionalInterface method에 익명구현객체가 된다.
        mfi = () -> {
            String str = "method call1";
            System.out.println(str);
        };

        mfi.method();

        mfi = () -> {
            System.out.println("method call2");
        };

        mfi.method();

        mfi = () -> System.out.println("method call3");
        mfi.method();

        //람다식은 익명 구현 객체와 같다.
        mfi = new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("method call4");
            }
        };

        mfi.method();
    }
}

@FunctionalInterface  //컴파일러는 하나의 메서드가 있는지 컴파일 단계에서 체크한다.
public interface MyFunctionalInterface {
    //함수적 인터페이스가 된다.
    public void method();
    //컴파일 오류 발생 [확인]
    //public void method2();
}

/**
 * 메서드를 하나만 가지고 있다면 함수적인터페이스가 된다.
 * @FunctionalInterface 가 필수는 아니다.
 */

매개변수가 있는 람다식

public class MyfunctionalExample {
    public static void main(String[] args){
        MyFunctionalInterface mfi;

        mfi = (x) -> {
            int result = 5 * x;
            System.out.println(result);
        };

        mfi.method(2);

        //약식표현
        mfi = x -> System.out.println(5 * x);
        mfi.method(3);

    }
}

@FunctionalInterface
public interface MyFunctionalInterface {
    //매개변수 하나 추가
    public void method(int x);
}

리턴값이 잇는 람다식


public class MyfunctionalExample {
    public static void main(String[] args) {
        MyFunctionalInterface mfi;
        //MyFunctionalInterface -> method가 실행될 때 람다식이 실행된다.

        mfi = (x, y) -> {
            int result = x + y;
            return result;
        };

        System.out.println(mfi.method(2, 5));

        //계산기 같은 확장 가능
        mfi = (x, y) -> {
            int result = x * y;
            return result;
        };

        System.out.println(mfi.method(2, 5));

        //약식
        mfi = (x, y) -> x + y;
        System.out.println(mfi.method(2, 5));

        //약식
        mfi = (x, y) -> sum(x, y);
        System.out.println(mfi.method(2, 5));

        //약식2 정적 메서드 테스트
//        mfi = (x,y) -> sum2(x,y); //정적메서드만 가능함
//        System.out.println(mfi.method(2,5));

/**
 * 궁금
 * 람다와 스트림이란 ?
 */
    }

    /**
     * 람다안에서는 정적 메서드만 사용이 가능하다.
     *
     * @param x
     * @param y
     * @return
     */
    public static int sum(int x, int y) {
        return x + y;
    }

    /**
     * 람다안에서는 정적 메서드만 사용이 가능하다.
     *
     * @param x
     * @param y
     * @return
     */
    public int sum2(int x, int y) {
        return x + y;
    }
}

@FunctionalInterface
public interface MyFunctionalInterface {
    //매개변수 하나 추가
    public int method(int x, int y);
}

###4절 클래스 멤버와 로컬 변수 사용

  • 클래스의 멤버 사용
    • 람다식 실행 블록에는 클래스의 멤버인 필드와 메서드를 제약 없이 사용할 수 있다.
    • 람다식 실행 블록내에서 this는 람다식을 실행한 객체의 참조이다.
public class UsingThis {
  public int outterField = 10;
  public int field = 10;

  class Inner { //람다식을 실행한 객체
      int innerField = 20;
      int field = 20;

      void method() {
          MyFunctionalInterface mfi = () -> {
              System.out.println("outterField : " + outterField);
              System.out.println("innerfield : " + innerField);

              System.out.println("outterField : " + UsingThis.this.field);
              System.out.println("innerfield : " + this.field);
          };
          mfi.method();
      }
  }
}

공통 함수적 인터페이스

@FunctionalInterface
public interface MyFunctionalInterface {
    //매개변수 하나 추가
    public void method();
}

묵시적으로 final 적용이 된다.

public class UsingLocalVariableExample {
    public static void main(String[] args){
        UsingLocalVariable ulv = new UsingLocalVariable();
        ulv.method(40);
    }
}

public class UsingLocalVariable {

    void method(int arg) {
        int localVar = 40;

//         final이 되어야 한다. 묵시적으로 final 적용이 된다.
//         주석을 풀게 되면 오류를 확인할 수 있다.
//        arg = 1;
//        localVar = 20;

        MyFunctionalInterface mfi = () -> {
            System.out.println("arg : " + arg);
            System.out.println("localVar : " + localVar);
        };
        mfi.method();
    }
}

외부 내부 클래스 호출

public class UsingThis {
    public int outterField = 10;
    public int field = 10;

    class Inner {
        int innerField = 20;
        int field = 20;

        void method() {
            MyFunctionalInterface mfi = () -> {
                System.out.println("outterField : " + outterField);
                System.out.println("innerfield : " + innerField);

                //field 의 이름의 같은경우
                //바깥클래스 호출
                System.out.println("outterField : " + UsingThis.this.field);
                //내부클래스 호출
                System.out.println("innerfield : " + this.field);
            };
            mfi.method();
        }
    }
}

public class UsingThisExample {
    public static void main(String[] args) {
        //중첩클래스에서 바깥(외부) 클래스의 객체 생성
        UsingThis usingThis = new UsingThis();
        //중첩클래스에서 내부 클래스의 객체 생성
        UsingThis.Inner inner = usingThis.new Inner();
        inner.method();

    }
}

5절 표준 API의 함수적 인터페이스

  • 한개의 추상 메서드를 가지는 인터페이스 들은 모두 람다식 사용가능

RunnableExample 클래스

public class RunnableExample {
    public static void main(String[] args){
        //잘 사용하지 않음
        Runnable runnable = () -> {
          for(int i=0; i<10; i++){
              System.out.println(i);
          }
        };

        Thread thread = new Thread(runnable);
        thread.start();

        //일반적으로 이렇게 사용
        Thread thread2 = new Thread(() -> {
          for(int i=0; i<10; i++){
              System.out.println(i);
          }
        });
        thread2.start();
    }
}

Runnable 인터페이스

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run(); //하나의 추상메서드를 가지고 있다.
}
  • 자바 8부터 표준 API로 제공되는 함수적 인터페이스
    • java.util.function 패키지에 포함되어 있다.
    • 매개타입으로 사용되어 람다식을 매개값으로 대입할 수 있도록 해준다.

5가지 함수적 인터페이스 특징 ~

  • Consumer - 데이터를 소비한다. - 매개값은 사용하고 리턴값은 없음 - 소비
  • Supplier - 공급자 데이터를 리턴 - 매개값은 없고 리턴값 있음 - 공급
  • Function - 데이터 변환 시 사용 - 매개값 사용 - 매개값 변환 - 리턴값 존재
  • Operator - 연산 - 매개값 사용 - 연산 후 - 리턴값 존재
  • Predicate - 매개값을 조사해서 true, false를 리턴

Comsumer, Function Operator

  • andThen(), compose() 디폴트 메서드
    • 함수적 인터페이스가 가지고 있는 디폴트 메서드이다.
    • 두개의 함수적 인터페이스를 순차적으로 연결해서 실행한다.
    • 첫번째 리턴값을 두번째 매개값으로 제공해서 최종 결과값 리턴한다.
    • andThen()과 compose()의 차이점은 어떤 함수적 인터페이스 부터 처리하느냐에 따름

Predicate

  • and(), or(), negate() 디폴트 메서드 isEqual() 정적 메서드
    • and() == &&와 대응
    • or() == || 과 대응
    • negate() == ! 과 대응
    • isEqual()
 Objects.equals(soruce, target)       
source targer 리턴값
null null true
null or not null null or not null false
not null not null source.equals(targer) 의 리턴값

###6절 메서드 참조

  • 메서드를 참조해서 매개변수의 정보 및 리턴타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.

종종 람다식은 기존 메서드를 단순하게 호출만 하는 경루가 있다.

(left, right) -> Math.max(left, right) //실행부 확인 시 매개 변수 값이 같을 경우 메서드 참조 가능 (줄여보자)
Math :: max; //메서드 참조
  • 메서드 참조도 람다식과 마찬가지로 인터페이스 익명 구현 객체로 생성된다.
    • 타겟 타입에서 추상 메서드의 매개변수 및 리턴 차입에 따라 메서드 참조도 달라진다. (타입과 매개값이 같은 경우 사용 가능)

정적메서드, 인스턴스메서드 참조


public class MethodReferencesExample {
    public static void main(String[] args){
        IntBinaryOperator operator;

        //정적 메소드 참조
        operator = (x,y) -> Calculator.staticMethod(x,y);
        System.out.println("결과1 : " + operator.applyAsInt(1,2));

        operator = Calculator :: staticMethod;
        System.out.println("결과2 : " +operator.applyAsInt(3,4));

        //인스턴스 메소드 참조
        Calculator obj = new Calculator();
        operator = (x,y) -> obj.instanceMethod(x,y);
        System.out.println("결과3 : " +operator.applyAsInt(5,6));

        operator = obj :: instanceMethod;
        System.out.println("결과4 : " +operator.applyAsInt(7,8));

        //함수적 인터페이스 익명 구현 객체로 만들어 짐으로 함수적 인터페이스의 변수에다가 대입이 가능하다.
    }
}

public class Calculator {
    public static int staticMethod(int x, int y) {
        return x + y;
    }

    public int instanceMethod(int x, int y){
        return x + y;
    }
}
  • 생성자 참조
(a,b) -> {return new 클래스(a,b)} => 클래스 :: new (타입과 매개 변수가 같아야 함)

매개값의 수에 따라서 생성자가 생성 된다.

public class ConstructorReferencesExample {

    public static void main(String[] args){
        //매개변수와 타입이 맞는 생성자가 생성된다.
        Function<String, Member> function1 = Member::new;
        Member membe1 = function1.apply("변");

        BiFunction<String, String, Member> function2 = Member::new;
        Member member2 = function2.apply("변", "whydda");

        Function<String, Member> function3 = Member::new;
        Member membe3 = function1.apply("김");
    }
}

public class Member {
    private String name;
    private String id;

    public Member() {
        System.out.println("Member() 실행");
    }

    public Member(String name) {
        this.name = name;
        System.out.println("Member(String name) 실행");
    }

    public Member(String name, String id) {
        this.name = name;
        this.id = id;
        System.out.println("Member(String name, String id) 실행");
    }
}
  • 회고
    역시 3번 이상은 읽어봐야할꺼 같다.
    정리를 한다고 했는데 혹시나 좀 이상한 부분 있으면 알려주세요 :)
  • 참고링크

이것이자바다 강의