자바 - 람다(Ramda)

업데이트:

java-logo

람다(Ramda)

자바의 코딩 최소단위는 클래스이다. 메소드를 쓰려면 반드시 클래스 안에 메소드를 생성해야만 쓸 수 있다. 이러한 객체지향 코딩에 때문에 간단한 함수위주 코딩을 하고싶었던 자바 개발자들은 불편함을 느꼈다. 그래서 등장한것이 람다문법이다.

람다 문법은 메서드 한개짜리 인터페이스를 구현한 익명 클래스를 람다식을 통해 더 간단히 만들 수 있다.

람다 문법 규칙

  • 추상메서드를 하나만 갖고있는 인터페이스에 대해 람다 문법으로 익명 클래스를 만들 수 있다.
  • 추상메서드가 두개라면 람다 문법으로 구현할 수 없다.
  • 여러개의 메서드가 있다 하더라도 추상메서드가 한개면 람다 문법 가능.
  • 인터페이스가 아닌 추상 클래스는 람다 구현의 대상이 아니다!!
  • 즉 람다는 메서드 한개짜리 인터페이스를 익명클래스로 구현한 문법이다.

람다 문법

interface Player {
    void play();
  }

  public static void main(String[] args) {
      // 인터페이스를 구현한 익명클래스
    Player p1 = new Player() {
      @Override
      public void play() {
        System.out.println("익명 클래스");
      }
    };
    p1.play();

      // 익명클래스를 람다식으로 변환
    Player p2 = () -> System.out.println("익명 클래스");
    p2.play();

}

람다 파라미터

public class Exam0130 {
  interface Player {
    void play(String name);
  }
  public static void main(String[] args) {
    // 1) 파라미터는 괄호() 안에 선언한다.
    Player p1 = (String name) -> System.out.println(name + "님 환영합니다.");
    p1.play("홍길동");

    // 2) 파라미터 타입을 생략할 수 있다.
    Player p2 = (name) -> System.out.println(name + "님 환영합니다.");
    p2.play("홍길동");

    // 3) 파라미터가 한 개일 때는 괄호도 생략할 수 있다.
    Player p3 = name -> System.out.println(name + "님 환영합니다.");
    p3.play("홍길동");
  }
}

  • 파라미터가 여러개일 때는 괄호를 생략할 수 없다.
public class Exam0140 {
  interface Player {
    void play(String name, int age);
  }

  public static void main(String[] args) {
    // 1) 파라미터는 괄호() 안에 선언한다.
    Player p1 = (String name, int age) -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
    p1.play("박민섭", 28);

    // 2) 파라미터 타입을 생략할 수 있다.
    Player p2 = (name, age) -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
    p2.play("최민섭", 27);

    // 3) 파라미터가 여러 개일 때는 괄호를 생략할 수 없다.
    //    Player p3 = name, age -> System.out.printf("%s(%d)님 환영합니다.",name, age);
    //    p3.play("최민섭", 29);
  }
}

람다 아규먼트(argument) 활용

아규먼트 자리에 lambda 문법을 바로 사용할 수 있다.

public class Exam0312 {
  static interface Player {
    void play();
  }

  static void testPlayer(Player player) {
    player.play();
  }

  public static void main(String[] args) {
   	// 아규먼트에 익명클래스를 둘 수 있다.
    testPlayer(new Player() {
      @Override
      public void play() {
        System.out.println("실행!");
      }
    });
      
    // 익명클래스를 람다로 바꿀 수 있다.
    testPlayer(() -> System.out.println("람다 아규먼트 실행!"));
  }
}

아규먼트에 여러 개의 문장이 있는 경우 블럭 {}을 생략할 수 없다.

public class Exam0330 {
  static interface Calculator {
    int compute(int a, int b);
  }
  static void test(Calculator c) {
    System.out.println(c.compute(100, 200));
  }
  public static void main(String[] args) {
    // 여러 문장을 실행하는 경우 블럭{}으로 감싸야 한다.
    test((a, b) -> {
      int sum = 0;
      for(int i = a; i <= b; i++) {
        sum += i;
      }
      return sum;
    });
  }
}

람다구현 - 메서드 레퍼런스

  • 메서드 한 개짜리 인터페이스의 구현체를 만들 때 기존 메서드를 람다 구현체로 사용할 수 있다.
  • 단, 인터페이스에 선언된 메서드의 규격(파라미터 타입 및 개수, 리턴 타입)과 일치해야 한다.
  • 문법 =>
    • 스태틱 메서드 : 클래스명::메서드명
    • 인스턴스 메서드 : 인스턴스명::메서드명

아래 예시는 스태틱 메서드로 람다 구현을 한 것이다.


public class Exam0510 {
  static class MyCalculator{
    public static int plus(int a, int b) {return a + b;}
    public static int minus(int a, int b) {return a - b;}
    public static int multiple(int a, int b) {return a * b;}
    public static int divide(int a, int b) {return a / b;}

  }
  static interface Calculator{
    int compute(int x, int y);
  }
  public static void main(String[] args) {
    Calculator c1 = MyCalculator::plus;
      // 위 코드는 내부적으로 아래와 같다.
      //Calculator c1 = new Calculator() {
      //@Override
      //public int compute(int a, int b) {
       // return MyCalculator.plus(a, b);
      //}
   // };
    Calculator c2 = MyCalculator::minus;
    Calculator c3 = MyCalculator::multiple;
    Calculator c4 = MyCalculator::divide;

    System.out.println(c1.compute(200, 17));
    System.out.println(c2.compute(200, 17));
    System.out.println(c3.compute(200, 17));
    System.out.println(c4.compute(200, 17));
  }
}

인터페이스에 정의된 메서드가 생성자의 형식과 일치한다면 메서드 레퍼런스로 생성자를 지정할 수도 있다.

public class Exam0710 {
  static interface ListFactory {
    List create();
  }
  public static void main(String[] args) {
    ListFactory f1 = ArrayList::new;

    // 인터페이스의 메서드를 호출하면
    // 지정된 클래스의 인스턴스를 만든 후 생성자를 호출한다.
    List list = f1.create(); // new ArrayList();

    System.out.println(list instanceof ArrayList);
    System.out.println(list.getClass().getName());
  }
}

람다 정리 - 인터페이스 구현체를 만드는 다양한 방법

public class Exam0810 {

  interface Factory {
    Object create();
  }

  static class Car {}

  public static void main(String[] args) {
    // 1) 로컬 클래스로 인터페이스 구현체를 만든다.
    class CarFactory implements Factory {
      @Override
      public Object create() {
        return new Car();
      }
    }
    Factory f1 = new CarFactory();
    Car car = (Car) f1.create();

    // 2) 익명 클래스로 인터페이스를 구현체로 만든다.
    Factory f2 = new Factory() {
      @Override
      public Object create() {
        return new Car();
      }
    };
    Car car2 = (Car) f2.create();

    // 3) 람다로 인터페이스 구현체를 만든다.
    Factory f3 = () -> new Car();
    Car car3 = (Car) f3.create();

    // 4) 기존에 존재하는 메서드로 인터페이스 구현체를 만든다.
    Factory f4 = Exam0810::createCar;
    Car car4 = (Car) f4.create();

    // 5) 기존 클래스의 생성자로 인터페이스 구현체를 만든다.
    Factory f5 = Car::new;
    Car car5 = (Car) f5.create();

    System.out.println("완료!");
  }

  public static Car createCar() {
    return new Car();
  }
}

댓글남기기