HOME > java > java8

Java8 - 메소드 레퍼런스(Method Reference)

JSFollow04 Oct 2019

메소드 레퍼런스(Method Reference)는 Lambda 표현식을 더 간단하게 표현하는 방법입니다.

예를들어, 다음은 람다식으로 Hello를 출력하는 코드입니다. (Consumer는 어떤 객체를 입력받아 void를 출력시키는 함수형 인터페이스입니다)

Consumer<String> func = text -> System.out.println(text);
func.accept("Hello");
// 실행 결과
// Hello

위의 람다식은 다음과 같이 System.out::println라는 메소드 레퍼런스로 표현할 수 있습니다. String 인자 1개를 받아 void를 출력시키는 함수라는 의미가 생략되어있습니다.

Consumer<String> func = System.out::println;
func.accept("Hello");
// 실행 결과
// Hello

메소드 레퍼런스는 ClassName::MethodName 형식으로 입력합니다. 메소드를 호출하는 것이지만 괄호()는 써주지 않고 생략합니다.

위의 예제처럼, 메소드 레퍼런스에는 많은 코드가 생략되어있기 때문에 사용하려는 메소드의 인자와 리턴 타입을 알고 있어야 합니다.

메소드 레퍼런스 형태로 작성되는 람다식이 매우 많고, 직접 코딩해보면 모든 코드를 다 써주는 것이 번거롭게 느낄 수 있습니다. 이런 경우 메소드 레퍼런스를 사용하면 매우 편리할 수 있습니다.

메소드 레퍼런스는 사용하는 패턴에 따라 다음과 같이 분류할 수 있습니다.

  • Static 메소드 레퍼런스
  • Instance 메소드 레퍼런스
  • Constructor 메소드 레퍼런스

Static 메소드 레퍼런스

Static method를 메소드 레퍼런스로 사용하는 케이스입니다.

아래 코드에서 변수 exe는 람다식으로 작성한 것이고, 변수 exe2는 메소드 레퍼런스로 작성되었습니다. printSomething 메소드는 인자1개를 받아 void를 리턴하는 static 메소드입니다.

interface Executable {
    void doSomething(String text);
}

public static class Printer {
    static void printSomething(String text) {
        System.out.println(text);
    }
}

public static void main(String args[]) {
    Executable exe = text -> Printer.printSomething(text);
    Executable exe2 = Printer::printSomething;
    exe.doSomething("do something");
    exe2.doSomething("do something");
}
// 실행 결과
// do somemthing
// do somemthing

다음 코드는 Consumer를 사용하여 위의 코드를 리팩토링했습니다. 결과는 동일합니다.

Consumer<String> consumer = Printer::printSomething;
consumer.accept("do something");

Consumer<클래스>는 자바에서 제공하는 함수형 인터페이스입니다. 위의 예제에서 Executable와 같은 인터페이스를 매번 만들기 어렵기 때문에 자바에서 기본적으로 인터페이스를 제공하고 있습니다. Consumer은 String 1개를 인자로 받아 void를 리턴하는 메소드를 갖고 있는 인터페이스입니다.

다음 예제는 Stream과 메소드 레퍼런스를 함께 사용하였습니다. 원래는 아래 1번처럼 람다식으로 길게 작성하던것을 2번과 같이 메소드 레퍼런스를 이용하면 간단하게 표현할 수 있습니다.

List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
// 1. lambda expression
companies.stream().forEach(company -> System.out.println(company));
// 2. static method reference
companies.stream().forEach(System.out::println);

Instance 메소드 레퍼런스

Instance 메소드 레퍼런스의 메소드는 static이 아니고 객체의 메소드를 의미합니다.

다음 코드는 Company 객체의 이름을 출력하는 예제입니다. 먼저 람다식으로 작성해보았습니다.

public static class Company {
    String name;
    public Company(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

public static void main(String args[]) {
    List<Company> companies = Arrays.asList(new Company("google"),
        new Company("apple"), new Company("samsung"));
    companies.stream().forEach(company -> company.printName());
}
// 실행 결과
// google
// apple
// samsung

위의 코드에서 company -> company.printName()는 람다식입니다. company객체가 인자로 전달되면 company 객체의 printName()메소드를 호출하라는 뜻인데요.

이것을 메소드 레퍼런스로 표현하면 Company::printName으로 쓸 수 있습니다. forEach에서 company객체가 전달될 것을 알기 때문에 이렇게 써주면 위처럼 인스턴스의 메소드를 호출해주는 의미로 이해합니다.

List<Company> companies1 =
        Arrays.asList(new Company("google"), new Company("apple"), new Company("samsung"));
companies1.stream().forEach(Company::printName);

다음은 String::length를 사용하는 예제입니다. 이 함수는 스트링 객체의 길이를 리턴해주는 instance 메소드입니다. mapToInt()가 String을 Int로 변환되길 예상하고 있습니다.

List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
companies.stream()
         .mapToInt(String::length) // 람다식: company -> company.length()
         .forEach(System.out::println);
// 실행 결과
// 6
// 5
// 6
// 5
// 7

Instance 메소드 레퍼런스는 Static 메소드 레퍼런스와 헷갈릴 수 있는데, 요구하는 인자와 리턴타입을 이미 알고 있다면 어렵지 않게 사용할 수 있습니다.

Constructor 메소드 레퍼런스

Constructor 메소드 레퍼런스는 Constructor를 생성해주는 코드입니다.

다음 예제는 람다식으로 생성자를 호출하는 코드입니다.

public static class Company {
    String name;
    public Company(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

public static void main(String args[]) {
    List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
    companies.stream()
            .map(name -> new Company(name))
            .forEach(company -> company.printName());
}
// 실행 결과
// google
// apple
// google
// apple
// samsung

위 코드를 메소드 레퍼런스를 사용하여 리팩토링하였습니다. Company::new의 의미는 name -> new Company(name)와 같습니다.

List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
companies.stream()
        .map(Company::new)
        .forEach(Company::printName);

위의 코드에서 map은 String 인자가 전달되고, Company로 리턴되길 예상하고 있습니다. Company::new는 String을 인자로 Company 객체를 생성하여 리턴해줍니다.

정리

메소드 레퍼런스는 자주 사용되는 패턴의 람다식을 간단하게 쓸 수 있는 방법이라고 정의할 수 있습니다. 그래서 어려운 것이라고 생각하지 않고, 특정 패턴의 람다식을 줄여서 쓴다고 생각하시면 좋을 것 같습니다.

참고