모르지 않다는 것은 아는것과 다르다.

Java

람다 표현식

채마스 2022. 2. 26. 00:42

람다식

  • 자바는 객체지향 프로그래밍 언어이다. 그렇기 때문에 기능을 수행하기 위해서는 객체를 만들고 그 객체 내부에 멤버 변수를 선언하고, 그 기능을 수행하는 메소드를 구현해야한다.
  • 하지만 자바 8부터는 함수형 프로그래밍 방식을 지원하고 이를 람다식 이라고 한다.
  • 함수의 구현과 호출만으로 프로그래밍이 수행돠는 방식이다.





함수형 프로그래밍의 장점

  • 함수형 프로그래밍은 순수함수를 구현하고 호출하기 때문에 외부 자료에 부수적인 영향(side effect) 를 주지 않도록 구현하는 방식이다.
    • 여기서 순수함수란? -> 매개변수만을 사용하여 만드는 함수를 말한다.
  • 함수를 기반으로 하는 프로그래밍이고 입력받는 자료 이외에 외부 자료를 사용하지 않아 여려 자료가 동시에 수행되는 병렬처리가 가능하다.
  • 함수형 프로그래밍은 함수의 기능이 자료에 독립적임을 보장한다.
  • 이는 동일한 자료에 대해 동일한 결과를 보장하고, 다양한 자료에 대해 같은 기능을 수행할 수 있다.





람다 표현식

  • 람다
    • (인자 리스트) -> {바디}
    • ex>
      Supplier<Integer> get10 = () -> 10; // 입력값 없고 10을 리턴한다.
      BiFunction<Integer, Integer, Integer> get10 = (a, b) -> a + b; // a 와 b 를 입력받아서 a+b를 리턴
  • 인자 리스트
    • 인자가 없을 때: ()
    • 인자가 한개일 때: (one) 또는 one
    • 인자가 여러개 일 때: (one, two)
    • 인자의 타입은 생략 가능, 컴파일러가 추론(infer)하지만 명시할 수도 있다. (Integer one, Integer two)
  • 바디
    • 화상표 오른쪽에 함수 본문을 정의한다.
    • 여러 줄인 경우에 { }를 사용해서 묶는다.
    • 한 줄인 경우에 생략 가능, return도 생략 가능.

 

  • 변수 캡처 (Variable Capture)
  private void run() {
      int baseNumber = 10; // effective final -> 사실상 final

      //로컬 클래스
      class LocalClass {
          void printBaseNumber() {
              int baseNumber = 11; // 쉐도윙 된다.
              System.out.println(baseNumber); // 11
          }
      }

      //익명 클래스
      Consumer<integer> integerConsumer = new Consumer<Integer>() {
          @Override
          public void accept(Integer integer) {
              System.out.println(baseNumber);
          }
      };

      //람다
      IntConsumer printInt = (i) -> { // 만약 i 자리에 baseNumber가 오면 오류가 나온다. ->  쉐도잉이 안되기 때문이다.
          System.out.println( i + baseNumber);
      };

  }
  • 로컬 변수 캡처
    • final이거나 effective final 인 경우에만 참조할 수 있다.
    • 그렇지 않을 경우 concurrency 문제가 생길 수 있어서 컴파일가 방지한다.
  • effective final
    • 이것도 역시 자바 8부터 지원하는 기능으로 “사실상" final인 변수.
    • final 키워드 사용하지 않은 변수를 익명 클래스 구현체 또는 람다에서 참조할 수 있다.
  • 익명 클래스 구현체와 달리 ‘쉐도윙’하지 않는다.
    • 익명 클래스는 새로 스콥을 만들지만, 람다는 람다를 감싸고 있는 스콥과 같다.






코드 예시

@FuctionalInterface
public interface Add {

  public int add (int a, int b);
}
  • 위와 같이 Add 라는 functional interface 를 선언한다.
  • @FuctionalInterface 가 붙으면 메소드를 추가할 수 없다. -> 람다식으로 호출할 메소드가 모호해지기 때문이다.
public class Main {
  public static void main(String[] args){

    Add add = (x, y) -> {return x+y;};
    System.out.println(add.add(2,3));
  }
}
  • 위와 같이 반환 값을 Add 인터페이스로 받아서 실행 시킬 수 있다.
  • Add add = (x, y) -> {return x+y;}; 는 Add add = (x, y) -> x+y; 로 생략해서 사용할 수 있다.
  • 자바는 어떤 방식으로 람다식 기능을 제공하고 있을까? -> 익명 내부 클래스로 제공하고 있다.
public class Main {
  public static void main(String[] args){

    Add add = (x, y) -> {return x+y;};
    System.out.println(add.add(2,3));

    // 익명 내부 클래스
    Add add2 = new Add(){
      @Override void add(int a, int b) {
        return a+b;
      }
    }
  }
}
  • 내부적으로 위와 같이 익명 내부 클래스가 생성되고, 그 익명 내부 클래스가 호출되는 것이다.
  • 위에서 구현한 람다식은 변수와 같이 매개변수와 반환값으로도 사용될 수 있다.





메소드 레퍼런스

 

  • 람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현할 수 있다.
  • 스태틱 메소드 참조
    • 타입::스태틱 메소드
    • ex>
  public class Greeting {
      private String name;

      public Greeting(){

      }

      public Greeting(String name){
          this.name = name;
      }

      public String hello(String name){
          return "hello " + name;
      }

      public Static String hi(String name){
          return "hi" + name;
      }
  }
  UnaryOperator<String> hi = Greeting::hi; //static 메서드 참조
  • 특정 객체의 인스턴스 메소드 참조
    • 객체 레퍼런스::인스턴스 메소드
    • ex>
  Greeting greeting = new Greeting();
  UnaryOperator<String> hello = greeting::hello;
  System.out.println(hello.apply("hyunwook"));
  • 임의 객체의 인스턴스 메소드 참조
    • 타입::인스턴스 메소드
  • 생성자 참조
    • 타입::new
    • ex>
  Supplier<Greeting> newGreeting = Greeting::new;
  Greeting greeting = newGreeting.get(); // 이 시점에 객체가 생성된다.
  • 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다.
    • ex>
  Function<String, Greeting> newGreeting = Greeting::new;
  Greeting greeting = newGreeting.apply("hyunwook");
  System.out.println(greeting.getName()); // "hyunwook" 이 출력된다.
  • 입력값이 있는 생성자는 Function으로 받는다.
    • 리턴값 또는 생성한 객체는 람다의 리턴값이다.




REFERENCES

  • 백기선님의 더 자바, Java 8

'Java' 카테고리의 다른 글

인터페이스 기본 메소드와 스태틱 메소드  (0) 2022.02.26
상수와 리터럴  (0) 2022.02.26
내부 클래스  (0) 2022.02.26
Thread  (0) 2022.02.26
String,StringBuilder,StringBuffer  (0) 2022.02.26