[Java] 람다식

우쓰·2023년 11월 23일
0

Java

목록 보기
10/10

📙 Content

✔️ 람다식

☑️ 람다식이란?

자바는 함수형 프로그래밍을 위해 Java 8 부터 람다식을 지원한다.
람다식은 매개변수를 가진 중괄호 블록이다.
데이터 처리부는 람다식을 받아 매개변수에 데이터를 대입하고 실행시켜 처리한다.

인터페이스의 익명 구현 객체를 람다식으로 표현하려면 인터페이스는 단 하나의 추상 메소드를 가지고 있어야 한다 이것을 함수형 인터페이스라고 한다.

인터페이스가 함수형 인터페이스인지 검사해주는
@FunctionalInterface 어노테이션이 있다 선택사항이므로 꼭 붙여야하는 것은 아니다.

@FunctionalInterface
public interface Calculate {
  void cal(int a, int b);

}
public class LambdaExample {
  public static void main(String[] args) {
    run((a, b) -> {
      int result = a + b;
      System.out.println("result = " + result);
    });
  }

  public static void run(Calculate calculate) {
    int a = 10;
    int b = 20;
    calculate.cal(a, b);
  }
}

콘솔

result = 30

☑️ 매개변수가 없는 람다식

함수형 인터페이스의 추상 메소드에 매개변수가 없는 경우 람다식을 작성하는 방법은
실행문이 하나인 경우와 실행문이 두개인 경우로 나뉜다.

실행문이 하나인 경우
() -> 실행문
실행문이 둘 이상인 경우
() -> { 실행문 실행문 .. }

실행문이 하나일때는 중괄호 생략이 가능하다.

public interface Workable {
  void work();
}
public class Person {
  public void action(Workable workable) {
    workable.work();
  }
}
public class LambdaExample {
  public static void main(String[] args) {
    Person person = new Person();
    
    // 실행문이 두개 이상인 경우 중괄호
    person.action(() -> {
      System.out.println("세수를 합니다");
      System.out.println("옷을 입습니다");
    });
    
    // 실행문이 하나인 경우 중괄호 생략
    person.action(() -> System.out.println("신발을 신습니다."));
  }
}

다음은 클릭했을때 이벤트를 발생시키는 예제이다.
public class Button {
  @FunctionalInterface
  public static interface ClickListener{
    // 매개변수가 없는 추상 메소드
    void onClick();
  }

  private ClickListener clickListener;

  public void setClickListener(ClickListener clickListener) {
    this.clickListener = clickListener;
  }

  public void click() {
    this.clickListener.onClick();
  }
}
public class ButtonExample {
  public static void main(String[] args) {
    
    Button btnOk = new Button();
    //ClickListener인터페이스의 추상 메소드에 매개변수가 없어서 "()"로 표현이 가능하다. 
    btnOk.setClickListener(() ->
        System.out.println("OK 버튼을 클릭했습니다.")
    );
    btnOk.click();
    
    Button btnCancel = new Button();
    btnCancel.setClickListener(() ->
            System.out.println("Cancel 버튼을 클릭했습니다.")
    );
    btnCancel.click();
  }
}

콘솔

OK 버튼을 클릭했습니다.
Cancel 버튼을 클릭했습니다.

☑️ 매개변수가 있는 람다식

함수형 인터페이스의 추상 메소드에 매개변수가 있는 경우 람다식은

(매개변수, ...) -> {
	실행문;
    실행문;
}

매개변수 타입은 생략하는것이 일반적이기 때문에 이렇게 작성할 수 있다.

이때 매개변수가 하나일 경우 괄호를 생략할 수 있다.

매개변수 -> {
	실행문;
    실행문;
}

예제를 통해 이해해보자

Workable 와 Speakable 함수형 인터페이스가 있다.

public interface Workable {
  void work(String name, String job);
}
public interface Speakable {
  void speak(String content);
}

이 인터페이스를 매개변수로 갖는 메소드를 만들어준다.

public class Person {
  public void action1(Workable workable) {
    workable.work("홍길동", "프로그래밍");
  }
  public void action2(Speakable speakable) {
    speakable.speak("안녕하세요");
  }
}

함수형 인터페이스에 매개변수가 존재하는 경우 '()' 안에 매개변수를 넣어주는 예시이다.

public class LambdaExample {
  public static void main(String[] args) {
    Person person = new Person();
    /**
     * 매개변수가 두 개일 경우
     */
    // 실행문이 두 개인 경우
    person.action1((name, job) -> {
      System.out.println(name + "이 " );
      System.out.println(job + "을 합니다");
    });
    System.out.println("=======================");

    // 실행문이 한 개인 경우
    person.action1((name, job) ->
        System.out.println(name + "이 " + job + "을 합니다.")
    );

    System.out.println("=======================");

    /**
     * 매개변수가 한 개일 경우
     */
    // 실행문이 두 개인 경우
    person.action2((word) -> {
      System.out.println("\"" + word + "\"");
      System.out.println("라고 말합니다.");
    });
    System.out.println("=======================");

    // 실행문이 한 개인 경우
    person.action2((word) -> System.out.println("\"" + word + "\"라고 외칩니다."));
  }
}
홍길동이 
프로그래밍을 합니다
=======================
홍길동이 프로그래밍을 합니다.
=======================
"안녕하세요"
라고 말합니다.
=======================
"안녕하세요"라고 외칩니다.

람다식에서 매개변수가 있는 것과 없는 것의 차이는
매개변수가 있는 경우엔 '()'에 매개변수를 넣어준다는것.


☑️ 리턴값이 있는 람다식

메소드 리턴 값이 void 가 아닌 경우
실행문이 없고 return문이 하나만 있을 경우 중괄호와 return 키워드를 생략할 수 있다.

(매개변수, ...) -> {
	실행문;
    return 값
}

예제

public interface Calculable {
  double cal(double x, double y);
}
public class Person {
  public void action(Calculable calculable) {
    double result = calculable.cal(10, 4);
    System.out.println("결과 : " + result);
  }
}
public class LambdaExample {
  public static void main(String[] args) {

    Person person = new Person();

    // 실행문이 있는 경우
    person.action((x, y) -> {

      double result = x + y;
      return result;
    });

    // 리턴문만 있는 경우
    person.action((x, y) -> x + y);
  }
}

콘솔

결과 : 14.0
결과 : 14.0

☑️ 메소드 참조

메소드를 참조해서 매개변수의 정보를 알아내 람다식에서 불필요한 매개변수를 제거하는 것을 목적으로 한다.


Math 클래스의 max() 메소드를 호출하는 람다식은

(left, right) -> Math.max(left, right);

이 람다식은 두 개의 값을 Math.max() 메소드의 매개 값으로 전달하는 역할만 한다
메소드 참조를 이용하여 깔끔하게 처리할수 있다.

Math :: max;

매개변수가 그대로 전달이 된다면 굳이 중복 코딩을할 필요가 없다는 것


정적 메소드인 경우와 인스턴스 메소드인 경우 참조 방법이 다르다

정적 메소드인 경우

클래스 :: 메소드

인스턴스 메소드인 경우
참조변수 :: 메소드

예제를 통해 확인해보자

위의 Person클래스와 Calcuable 인터페이스를 동일하게 사용하고, Computer클래스와 main함수 클래스를 만들어준다

public class Computer {
  // 정적 메소드
  public static double staticMethod(double x, double y) {
    return x + y;
  }

  // 인스턴스 메소드
  public double instanceMethod(double x, double y) {
    return x * y;
  }
}
public class MethodRefExample {
  public static void main(String[] args) {
    Person person = new Person();
    
    // 정적 메소드
    // person.action((x, y) -> Computer.staticMethod(x, y));
    // 메소드 참조
    person.action(Computer :: staticMethod);
    
    // 인스턴스 메소드 
    Computer com = new Computer();
    // person.action((x, y) -> com.instanceMethod(x, y));
    // 메소드 참조
    person.action(com ::instanceMethod);
  }
}

출력

결과 : 14.0
결과 : 40.0

여기에서 드는 의문

만약 매개변수로 x, y를 받아서 (y, x)를 처리하는것도 메소드 참조가 가능할까?

이렇게 순서가 바뀐 경우엔 메소드 참조를 사용하지 못한다
메소드 참조는 매개변수 순서가 동일해야 한다.



☑️ 생성자 참조

생성자 참조는 객체 생성을 의미한다
람다식이 객체를 생성하고 리턴하도록 생성자 참조로 대치가 가능하다
(a, b) -> { return new 클래스 (a, b);}
이것을 생성자 참조로 표현하면
클래스 :: new

만약, 생성자가 오버로딩된 경우 컴파일러는 추상 메소드와 동일한 매개변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다.
생성자가 없는 경우는 컴파일 오류가 발생한다.

예제
함수형 인터페이스 두개 생성

public interface Creatable1 {
  public Member create(String id);
}
public interface Creatable2 {
  public Member create(String id, String name);
}
public class Member {
  private String id;
  private String name;

  public Member(String id) {
    this.id = id;
  }

  public Member(String id, String name) {
    this.id = id;
    this.name = name;
  }

  @Override
  public String toString() {
    return "Member{" +
        "id='" + id + '\'' +
        ", name='" + name + '\'' +
        '}';
  }
}
public class Person {
  public Member getMember1(Creatable1 creatable) {
    String id = "winter";
    Member member = creatable.create(id);
    return member;
  }

  public Member getMember2(Creatable2 creatable) {
    String id = "winter";
    String name = "한겨울";
    Member member = creatable.create(id, name);
    return member;
  }
}
public class ConstructorRefExample {

  public static void main(String[] args) {
    Person person = new Person();

    Member m1 = person.getMember1(Member::new);
    System.out.println(m1);
    System.out.println();

    Member m2 = person.getMember2(Member::new);
    System.out.println(m2);
  }
}

출력

Member{id='winter', name='null'}

Member{id='winter', name='한겨울'}

Member m1 = person.getMember1(Member::new);에서 Member::new
어떤 생성자를 의미할까?

Person 클래스에 getMember1메소드는 Creatable1 인터페이스에서 id만을 매개변수로 받기 때문에

  public Member(String id) {
    this.id = id;
  }

생성자를 의미한다.

0개의 댓글