# 메서드 참조

## 메서드 참조가 필요한 이유

**V1**

```java
import java.util.function.BinaryOperator;

public class MethodRefStartV1 {
    public static void main(String[] args) {
        BinaryOperator<Integer> add1 = (x, y) -> x + y;
        BinaryOperator<Integer> add2 = (x, y) -> x + y;

        System.out.println(add1.apply(1, 2)); // 3
        System.out.println(add2.apply(1, 2)); // 3
    }
}
```

두 정수를 더하는 간단한 연산을 수행하는 람다를 정의한 코드에는 다음과 같은 문제점이 있다.

1. 동일한 기능을 하는 람다를 여러 번 작성해야 한다.
2. 코드가 중복되어 있어 유지보수가 어려울 수 있다.
3. 만약 덧셈 로직이 변경되어야 한다면 모든 람다를 각각 수정해야 한다.

**V2**

```java
import java.util.function.BinaryOperator;

public class MethodRefStartV2 {
    public static void main(String[] args) {
        BinaryOperator<Integer> add1 = (x, y) -> add(x, y);
        BinaryOperator<Integer> add2 = (x, y) -> add(x, y);

        System.out.println(add1.apply(1, 2));
        System.out.println(add2.apply(1, 2));
    }

    public static int add(int x, int y) {
        return x + y;
    }
}
```

덧셈 로직을 별도의 `add()` 메서드로 분리하여 코드 중복 문제를 해결했다. 로직이 한 곳으로 모여 유지보수가 쉬워졌다. 그러나 여전히 남은 문제가 있다.

1. 람다를 작성할 때마다 `(x, y) -> add(x, y)`과 같은 코드를 반복해서 작성해야 한다.
2. 매개변수를 전달하는 부분이 장황하다.

**V3**

```java
import java.util.function.BinaryOperator;

public class MethodRefStartV3 {
    public static void main(String[] args) {
        //메서드 참조
        BinaryOperator<Integer> add1 = MethodRefStartV3::add;
        BinaryOperator<Integer> add2 = MethodRefStartV3::add;

        System.out.println(add1.apply(1, 2));
        System.out.println(add2.apply(1, 2));
    }

    public static int add(int x, int y) {
        return x + y;
    }
}
```

메서드 참조 문법을 사용하여 람다를 더욱 간단하게 표현할 수 있다.

**메서드 참조**는 **이미 정의된 메서드를 람다로 변환하여 더욱 간결하게 사용할 수 있도록 해주는 문법적 편의 기능**이다. 람다를 작성할 때 이미 정의된 메서드를 그대로 호출하는 경우라면 메서드 참조를 통해 더욱 직관적이고 간결한 코드를 작성할 수 있다.

**메서드 참조의 장점**

* 메서드 참조를 사용하면 코드가 더욱 간결해지고 가독성이 향상된다.
* 컴파일러가 자동으로 매개변수를 매칭하기 때문에 매개변수를 명시적으로 작성할 필요가 없다.
* 별도의 로직 분리와 함께 재사용성 역시 높아진다.

***

## 메서드 참조의 유형

메서드 참조는 **이미 정의된 메서드를 람다처럼 간결하게 표현**할 수 있게 해주는 문법이다. 즉 람다 내부에서 단순히 어떤 메서드를 호출만 하고 있을 경우 메서드 참조를 사용해 이를 축약해주는 문법이다.

이러한 메서드 참조에는 4가지 유형이 있다.

1. **정적 메서드 참조**
2. **특정 객체의 인스턴스 메서드 참조**
3. **생성자 참조**
4. **임의 객체의 인스턴스 메서드 참조**

다음 `Person` 클래스를 예로 각 유형에 대해 알아보자.

```java
public class Person {
    private String name;

    //생성자
    public Person() {
        this("Unknown");
    }

    //생성자(매개변수)
    public Person(String name) {
        this.name = name;
    }

    //정적 메서드
    public static String greeting() {
        return "Hello";
    }

    //정적 메서드(매개변수)
    public static String greetingWithName(String name) {
        return "Hello " + name;
    }
    
    //인스턴스 메서드
    public String introduce() {
        return "I am " + name;
    }

    //인스턴스 메서드(매개변수)
    public String introduceWithNumber(int number) {
        return "I am " + name + ", my number is " + number;
    }

    public String getName() {
        return name;
    }
}
```

**V1 - 매개변수가 없는 예제**

```java
import java.util.function.Supplier;

public class MethodRefEx1 {
    public static void main(String[] args) {
        // 1. 정적 메서드 참조
        Supplier<String> staticMethod1 = () -> Person.greeting();
        Supplier<String> staticMethod2 = Person::greeting; // 클래스::정적메서드

        System.out.println("staticMethod1: " + staticMethod1.get());
        System.out.println("staticMethod2: " + staticMethod2.get());

        // 2. 특정 객체의 인스턴스 참조
        Person person = new Person("Kim");
        Supplier<String> instanceMethod1 = () -> person.introduce();
        Supplier<String> instanceMethod2 = person::introduce; // 객체::인스턴스메서드

        System.out.println("instanceMethod1: " + instanceMethod1.get());
        System.out.println("instanceMethod2: " + instanceMethod2.get());

        // 3. 생성자 참조
        Supplier<Person> newPerson1 = () -> new Person();
        Supplier<Person> newPerson2 = Person::new; // 클래스::new

        System.out.println("newPerson1: " + newPerson1.get());
        System.out.println("newPerson2: " + newPerson2.get());
    }
}
```

> **👆 메서드 참조에서 ()를 사용하지 않는 이유**
>
> * 메서드 참조의 문법을 보면 메서드명 뒤에 `()`가 없다.
> * `()`는 메서드를 즉시 호출한다는 의미를 가진다. `()`가 없는 것은 메서드 참조를 하는 시점에는 메서드를 호출하는게 아니라 단순히 **메서드의 이름으로 해당 메서드를 참조만 한다는 뜻이다.**
> * 해당 메서드의 실제 호출 시점은 함수형 인터페이스를 통해서 이후에 이루어진다.

**V2 - 매개변수가 있는 예제**

```java
import java.util.function.Function;

public class MethodRefEx2 {
    public static void main(String[] args) {
        // 1. 정적 메서드 참조
        Function<String, String> staticMethod1 = name -> Person.greetingWithName(name);
        Function<String, String> staticMethod2 = Person::greetingWithName; // 클래스::정적메서드
        System.out.println("staticMethod1 = " + staticMethod1.apply("Kim"));
        System.out.println("staticMethod2 = " + staticMethod2.apply("Kim"));

        // 2. 인스턴스 메서드 참조(특정 객체의 인스턴스 메서드 참조)
        Person instance = new Person("Kim");
        Function<Integer, String> instanceMethod1 = n -> instance.introduceWithNumber(n);
        Function<Integer, String> instanceMethod2 = instance::introduceWithNumber; // 객체::인스턴스메서드
        System.out.println("instanceMethod1 = " + instanceMethod1.apply(1));
        System.out.println("instanceMethod2 = " + instanceMethod2.apply(1));

        // 3. 생성자 참조
        Function<String, Person> supplier1 = name -> new Person(name);
        Function<String, Person> supplier2 = Person::new; // 클래스::new
        System.out.println("newPerson = " + supplier1.apply("Kim"));
        System.out.println("newPerson = " + supplier2.apply("Lee"));
    }
}
```

* 매개변수가 있는 메서드 참조의 경우 매개변수를 생략한다. 매개변수가 여러 개라면 순서대로 전달된다.
* 함수형 인터페이스의 시그니처(매개변수와 반환 타입)가 이미 정해져 있고, 컴파일러가 그 시그니처를 바탕으로 메서드 참조와 연결해 주기 때문에 명시적으로 매개변수를 작성하지 않아도 자동으로 추론되어 호출된다.
* 따라서 매개변수를 포함한 메서드 호출도 메서드 참조를 사용하여 더욱 간편하게 작성할 수 있다.

**V3 - 임의 객체의 인스턴스 메서드 참조 예제**

```java
import java.util.function.Function;

public class MethodRef3 {
    public static void main(String[] args) {
        //4. 임의 객체의 인스턴스 메서드 참조(특정 타입의)
        Person kim = new Person("Kim");
        Person park = new Person("Park");
        Person lee = new Person("Lee");

        // 람다
        Function<Person, String> fun1 = (Person person) -> person.introduce();
        System.out.println("kim.introduce = " + fun1.apply(kim));
        System.out.println("park.introduce = " + fun1.apply(park));
        System.out.println("lee.introduce = " + fun1.apply(lee));

        // 메서드 참조, 타입이 첫 번째 매개변수가 됨
        // 그리고 첫 번째 매개변수의 메서드를 호출, 나머지는 순서대로 매개변수에 전달
        Function<Person, String> fun2 = Person::introduce; // 타입::인스턴스메서드
        System.out.println("kim.introduce = " + fun2.apply(kim));
        System.out.println("park.introduce = " + fun2.apply(park));
        System.out.println("lee.introduce = " + fun2.apply(lee));
    }
}
```

* `Function<Person, String>` 함수형 인터페이스에 `Person` 타입의 인스턴스를 인자로 받고, `String`을 반환하는 람다를 정의했다.
* 이 람다는 **매개변수로 지정한 특정 타입의 객체에 대해 동일한 메서드를 호출**하는 패턴을 보인다.
* 즉 **매개변수로 지정한 특정 타입의 임의 객체의 인스턴스 메서드를 참조**한다.
  * **매개변수로 지정한 타입** : `Person`
  * **임의 객체** : `kim`, `park`, `lee` 등 `Person` 타입을 구현한 어떠한 객체
  * **인스턴스 메서드** : `introduce()`

이러한 메서드 참조를 **특정 타입의 임의 객체의 인스턴스 참조**라 한다. 앞서 본 메서드 참조의 4가지 유형을 한번 정리해보자.

1. 정적 메서드 참조 : `클래스명::클래스메서드`
2. 특정 객체의 인스턴스 메서드 참조 : `객체명::인스턴스메서드`
3. 생성자 참조 : `클래스명::new`
4. 임의 객체의 인스턴스 메서드 참조 : `클래스명::인스턴스메서드`

여기서 **2. 특정 객체의 인스턴스 메서드 참조**와 **4. 임의 객체의 인스턴스 메서드 참조**가 비슷해 보이는데, 두 기능은 완전히 다른 기능이다. (기본적으로 둘다 **인스턴스 메서드**를 호출하지만 하나는 **객체명**을 사용하고, 하나는 **클래스명**을 사용하는 것이 다름)

* **`2`. 특정 객체의 인스턴스 메서드 참조**
  * 이 기능은 메서드 참조를 선언할 때부터 이름 그대로 특정 객체(인스턴스)를 지정해야 한다.
  * **특정 객체의 인스턴스 메서드 참조**는 선언 시점부터 이미 인스턴스가 지정되어 있다. 따라서 람다를 실행하는 시점에 인스턴스를 변경할 수 없다.
* **`4`. 임의 객체의 인스턴스 메서드 참조**
  * 이 기능은 메서드 참조를 선언할 때는 어떤 객체(인스턴스)가 대상이 될지 모른다.
  * 임의 객체의 인스턴스 메서드 참조는 선언 시점에 호출할 인스턴스를 지정하지 않는다. 대신에 호출 대상을 매개변수로 선언해두고, 실행 시점에 호출할 인스턴스를 받는다.
  * **실행 시점에 되어야 어떤 객체가 호출되는지** 알 수 있으므로 **임의 객체의 인스턴스 메서드 참조**라 한다.

정리하면 둘의 핵심적인 차이는 메서드 참조나 람다를 정의하는 시점에 호출할 대상 인스턴스가 고정되는 것인지 아닌지이다.

* **특정 객체의 인스턴스 메서드 참조**는 선언 시점에 호출할 **특정 객체가 고정된다.**
* **임의 객체의 인스턴스 메서드 참조**는 선언 시점에 메서드를 호출할 특정 객체가 고정되지 않는다. 대신에 실행 시점에 인자로 넘긴 **임의의 객체**가 사용된다.

***

## 메서드 참조 활용 예제

임의 객체의 인스턴스 메서드 참조는 꼭 필요해 보이지 않는다. 하지만 메서드 참조 유형 중에는 이 기능이 가장 많이 사용된다.

**예제 1 - 기본**

```java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

public class MethodRef4 {
    public static void main(String[] args) {
        List<Person> list = List.of(
                new Person("Kim"),
                new Person("Park"),
                new Person("Lee")
        );

        List<String> result1 = mapPersonToString(list, (Person p) -> p.introduce()); //람다
        List<String> result2 = mapPersonToString(list, Person::introduce);  //메서드 참조

        List<String> result3 = mapStringToString(result1, (String s) -> s.toUpperCase()); //람다
        List<String> result4 = mapStringToString(result2, String::toUpperCase); //메서드 참조
    }

    static List<String> mapPersonToString(List<Person> list, Function<Person, String> function) {
        List<String> result = new ArrayList<>();
        for (Person p : list) {
            result.add(function.apply(p));
        }
        return result;
    }

    static List<String> mapStringToString(List<String> list, Function<String, String> function) {
        List<String> result = new ArrayList<>();
        for (String s : list) {
            result.add(function.apply(s));
        }
        return result;
    }
}
```

**예제 2 -** [**MyStream**](https://geun-00.gitbook.io/geun-00-docs/java/adv_2/undefined-1#람다-활용---스트림) **적용**

```java
import java.util.List;

public class MethodRef5 {
    public static void main(String[] args) {
        List<Person> list = List.of(
                new Person("Kim"),
                new Person("Park"),
                new Person("Lee")
        );

        //람다
        List<String> result1 = MyStreamV3.of(list)
                                         .map(person -> person.introduce())
                                         .map(s -> s.toUpperCase())
                                         .toList();
        //메서드 참조
        List<String> result2 = MyStreamV3.of(list)
                                         .map(Person::introduce) // person -> person.introduce()
                                         .map(String::toUpperCase) // (String name) -> name.toUpperCase()
                                         .toList();
    }
}
```

**예제 3 - 매개변수가 여러 개인 경우**

```java
import java.util.function.BiFunction;

public class MethodRef6 {
    public static void main(String[] args) {
        // 4. 임의 객체의 인스턴스 메서드 참조(특정 타입의)
        Person person = new Person("Kim");

        // 람다
        BiFunction<Person, Integer, String> fun1 = (Person p, Integer number) -> p.introduceWithNumber(number);
        System.out.println("person.introduceWithNumber = " + fun1.apply(person, 1));

        // 메서드 참조
        // 타입이 첫 번째 매개변수가 됨, 그리고 첫 번째 매개변수의 메서드를 호출
        // 나머지는 순서대로 매개변수에 전달
        BiFunction<Person, Integer, String> fun2 = Person::introduceWithNumber; // 타입::메서드명
        System.out.println("person.introduceWithNumber = " + fun2.apply(person, 1));
    }
}
```

**정리**

* 람다 대신에 메서드 참조를 사용하면 코드가 더 간결해지고 의도가 더 명확하게 드러난다.
* 메서드 참조를 사용하면 람다 표현식을 더욱 직관적으로 표현할 수 있으며, 각 처리 단계에서 호출되는 메서드가 무엇인지 쉽게 파악할 수 있다.
* 람다로도 충분히 표현할 수 있지만, 내부적으로 호출만 하는 간단한 람다라면 **메서드 참조**가 더 짧고 명확하게 표현될 수 있으며 가독성을 높일 수 있다.
