Java - 메소드 레퍼런스(Method Reference) 이해하기

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

이 글에서는 메소드 레퍼런스의 기본적인 사용 방법 및 예제에 대해서 알아보겠습니다.

1. 메소드 레퍼런스(Method References)

메소드 레퍼런스는 람다식과 함께 사용될 수 있습니다.

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

Consumer<String> func = text -> System.out.println(text);
func.accept("Hello");

Output:

Hello

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

Consumer<String> func = System.out::println;
func.accept("Hello");

Output:

Hello

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

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

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

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

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

2. Static 메소드 레퍼런스

Static 메소드 레퍼런스는 Static method를 메소드 레퍼런스로 사용하는 것을 말합니다.

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

public class Example {

    public static void main(String[] args) {

        Executable exe = text -> Printer.printSomething(text);
        Executable exe2 = Printer::printSomething;
        exe.doSomething("do something");
        exe2.doSomething("do something");
    }

    interface Executable {
        void doSomething(String text);
    }

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

Output:

do somemthing
do somemthing

2.1 Executable을 Consumer로 대체

Custom 인터페이스인 Executable를 Java에서 기본으로 제공하는 함수형 인터페이스인 Consumer로 대체할 수 있습니다. 즉, 굳이 불필요한 인터페이스를 만들지 않고 기본으로 제공하는 인터페이스를 사용할 수 있다는 것입니다.

아래 코드는 Executable을 Consumer로 대체한 코드입니다. 실행 결과는 이전 코드와 동일합니다.

import java.util.function.Consumer;

public class Example {

    public static void main(String[] args) {

        Consumer<String> exe = text -> Printer.printSomething(text);
        Consumer<String> exe2 = Printer::printSomething;
        exe.accept("do something");
        exe2.accept("do something");
    }

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

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

2.2 Stream에서 메소드 레퍼런스 사용

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

import java.util.Arrays;
import java.util.List;

public class Example {

    public static void main(String[] args) {

        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);
    }
}

3. Instance 메소드 레퍼런스

Instance 메소드 레퍼런스는 static 메소드가 아닌, 객체의 멤버 메소드를 메소드 레퍼런스로 사용하는 것을 의미합니다.

다음 코드는 Company 객체의 이름을 출력하는 예제입니다. 먼저 메소드 레퍼런스를 사용하지 않고 람다식으로만 구현해보았습니다.

import java.util.Arrays;
import java.util.List;

public class Example {

    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());
    }

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

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

Output:

google
apple
samsung

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

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

import java.util.Arrays;
import java.util.List;

public class Example {

    public static void main(String[] args) {

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

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

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

3.1 Stream에서 메소드 레퍼런스 사용

문자열의 길이를 리턴하는 String.length() 함수를 메소드 레퍼런스로 표현하면 String::length가 됩니다.

mapToInt()가 String을 Int로 변환하는 함수입니다. Lamda로 구현하면 mapToInt(company -> company.length())가 되지만, 간단히 mapToInt(String::length)로 구현할 수 있습니다.

import java.util.Arrays;
import java.util.List;

public class Example {

    public static void main(String[] args) {

        List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
        companies.stream()
                .mapToInt(String::length)
//                .mapToInt(company -> company.length())  /* Lamda expression */
                .forEach(System.out::println);
    }
}

Output:

6
5
6
5
7

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

4. Constructor 메소드 레퍼런스

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

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

import java.util.Arrays;
import java.util.List;

public class Example {

    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());
    }

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

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

Output:

google
apple
google
apple
samsung

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

import java.util.Arrays;
import java.util.List;

public class Example {

    public static void main(String[] args) {

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

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

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

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

5. References

Loading script...

Related Posts

codechachaCopyright ©2019 codechacha