[Java] 람다식 ④

kiteB·2022년 2월 20일
0

Java2

목록 보기
9/36
post-thumbnail

[ 메소드 참조 ]

메소드 참조(Method Reference)란 말 그대로 메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에서 불필요한 매개 변수를 제거하는 것이 목적이다.

람다식은 종종 기존 메소드를 단순히 호출만 하는 경우가 많다.
예를 들어 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() 정적 메소드를 호출하는 람다식은 다음과 같다.

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

람다식은 단순히 두 개의 값을 Math.max() 메소드의 매개값을 전달하는 역할만 하기 때문에 다소 불편해 보인다. 다음과 같이 메소드 참조를 이용하면 매우 깔끔하게 처리할 수 있다.

Math :: max;	//메소드 참조

메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성되므로 타겟 타입인 인터페이스의 추상 메소드가 어떤 매개 변수를 가지고, 리턴 타입이 무엇인가에 따라 달라진다. IntBinaryOperator 인터페이스는 두 개의 int 매개값을 받아 int 값을 리턴하므로 Math :: max 메소드 참조를 대입할 수 있다.

IntBinaryOperator operator = Math :: max;	//메소드 참조

메소드 참조는 정적 또는 인스턴스 메소드를 참조할 수 있고, 생성자 참조도 가능하다.


1. 정적 메소드와 인스턴스 메소드 참조

정적 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고, 정적 메소드 이름을 기술하면 된다.

클래스 :: 메소드

인스턴스 메소드를 참조할 경우에는 먼저 객체를 생성한 다음, 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술하면 된다.

참조변수 :: 메소드

예제 | Calculator의 정적 및 인스턴스 메소드 참조

  • Calculator (정적 및 인스턴스 메소드)
public class Calculator {
    public static int staticMethod(int x, int y) {	//정적 메소드
        return x + y;
    }
    
    public int instanceMethod(int x, int y) {	//인스턴스 메소드
        return x + y;
    }
}
  • MethodReferenceExample (정적 및 인스턴스 메소드 참조)
import java.util.function.IntBinaryOperator;

public class MethodReferenceExample {
    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));

    }
}
  • 실행 결과
결과1: 3
결과2: 7
결과3: 11
결과4: 15

2. 매개 변수의 메소드 참조

메소드는 람다식 외부의 클래스 멤버일 수도 있고, 람다식에서 제공되는 매개 변수의 멤버일 수도 있다.

이전 예제는 람다식 외부의 클래스 멤버인 메소드를 호출하였지만, 다음과 같이 람다식에서 제공되는 a 매개 변수의 메소드를 호출해서 b 매개 변수를 매개값으로 사용하는 경우도 있다.

(a, b) -> { a.instanceMethod(b); }

다음과 같이 a 클래스 이름 뒤에 :: 기호를 붙이고 메소드 이름을 기술하여 메소드 참조로 표현할 수 있다.
작성 방법은 정적 메소드 참조와 동일하지만, a의 인스턴스 메소드가 참조되므로 전혀 다른 코드가 실행된다.

클래스 :: instanceMethod

예제 | 두 문자열이 대소문자 상관없이 동일한 알파벳으로 구성되어 있는지 비교

비교를 위해 String의 인스턴스 메소드인 compareToIgnoreCase()를 사용한다.
a.compareToIgnoreCase(b)로 호출될 때 사전 순으로 ab보다

  • 먼저 오면 음수를,
  • 동일하면 0을,
  • 나중에 오면 양수를 리턴한다.

사용된 함수적 인터페이스는 두 String 매개값을 받고 int 값을 리턴하는 ToIntBiFunction<String, String>이다.

  • ArgumentMethodReferenceExample (매개 변수의 메소드 참조)
import java.util.function.ToIntBiFunction;

public class ArgumentMethodReferenceExample {
    public static void main(String[] args) {
        ToIntBiFunction<String, String> function;

        function = (a, b) -> a.compareToIgnoreCase(b);
        print(function.applyAsInt("Java8", "JAVA8"));

        function = String :: compareToIgnoreCase;
        print(function.applyAsInt("Java8", "JAVA8"));
    }

    public static void print(int order) {
        if (order < 0) {
            System.out.println("사전순으로 먼저 옵니다.");
        } else if (order == 0) {
            System.out.println("동일한 문자열입니다.");
        } else {
            System.out.println("사전순으로 나중에 옵니다.");
        }
    }
}
  • 실행 결과
동일한 문자열입니다.
동일한 문자열입니다.

3. 생성자 참조

메소드 참조(Method Reference)는 생성자 참조도 포함한다. (생성자 참조는 객체 생성을 의미한다.)

단순히 메소드 호출로 구성된 람다식을 메소드 참조로 대치할 수 있듯이, 단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치할 수 있다.

다음 코드를 보면 람다식은 단순히 객체 생성 후 리턴만 한다.

(a, b) -> { return new 클래스(a, b); }

다음과 같이 클래스 이름 뒤에 :: 기호를 붙이고 new 연산자를 기술하여 생성자 참조로 표현할 수 있다.

생성자가 오버로딩되어 여러 개가 있을 경우, 컴파일러는 함수적 인터페이스의 추상 메소드와 동일한 매개 변수 타입과 개수를 가지고 있는 생성자를 찾아 실행한다. 만약 해당 생성자가 존재하지 않으면 컴파일 오류가 발생한다.

클래스 :: new

예제 | 생성자 참조를 이용해서 두 가지 방법으로 Member 객체 생성하기

  1. Function<String, Member> 함수적 인터페이스의 Member apply(String) 메소드를 이용해서 Member 객체 생성
  2. BiFunction<String, String, Member> 함수적 인터페이스의 Member apply(String, String) 메소드를 이용해서 Member 객체 생성하기

생성자 참조는 두 가지 방법 모두 동일하지만, 실행되는 Member 생성자가 다르다.

  • ConstructorReferencesExample (생성자 참조)
import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorReferencesExample {
    public static void main(String[] args) {
        Function<String, Member> function1 = Member :: new;
        Member member1 = function1.apply("angel");

        BiFunction<String, String, Member> function2 = Member :: new;
        Member member2 = function2.apply("신천사", "angel");
    }
}
  • Member (생성자 오버로딩)
public class Member {
    private String name;
    private String id;

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

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

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

    public String getId() {
        return id;
    }
}
  • 실행 결과
Member(String id) 실행
Member(String name, String id) 실행

[ 참고자료 ]

이것이 자바다 책

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글