본문 바로가기
인프런/김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍

[인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 7. 메서드 참조

by hxxyeoniii 2025. 5. 28.

메서드 참조가 필요한 이유

MethodRefStartV3.java

메서드 참조 사용 -> MethodRefStartV3::add 

package methodref.start;

import java.util.function.BinaryOperator;

public class MethodRefStartV3 {

    public static void main(String[] args) {
        // BinaryOperator<Integer> add1 = (x, y) -> x + y;
        // BinaryOperator<Integer> add1 = (x, y) -> add(x, y);
        BinaryOperator<Integer> add1 = MethodRefStartV3::add; // 메서드 참조 사용
        BinaryOperator<Integer> add2 = MethodRefStartV3::add;

        Integer result1 = add1.apply(1, 2);
        System.out.println(result1);
        Integer result2 = add2.apply(1, 2);
        System.out.println(result2);
    }

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

 

 

 

 

메서드 참조(Method Reference)

: 이미 정의된 메서드를 그대로 참조해 람다 표현식을 더 간결하게 작성하는 방법

: (x, y) -> add(x, y)라는 람다는 사실 매개변수 x와 y를 add 메서드에 전달하기만 하는 코드이므로, 클래스명::메서드명 형태로 간단히 표현할 수 있음

: 코드가 더욱 간결해지고 가독성이 향상됨 -> 컴파일러가 자동으로 매개변수 매칭

: 람다를 작성할 때, 이미 정의된 메서드를 그대로 호출하는 경우 메서드 참조를 통해 더욱 직관적이고 간결한 코드 작성 가능


메서드 참조 1 - 시작

(x, y) -> 클래스명.메서드명(x, y) // 기존 람다
클래스명::메서드명 // 메서드 참조

-> 람다와 메서드 참조는 동등하게 동작한다.

-> 메서드 참조는 람다가 단순히 어떤 메서드만 호출하는 경우, 이를 축약해주는 문법이라고 이해하면 된다.

 

 

 

메서드 참조의 4가지 유형

1. 정적 메서드 참조

: 이름 그대로 static 메서드 참조

: 클래스명::메서드명

: ex) Math::max, Integer::paresInt 등

 

2. 특정 객체의 인스턴스 메서드 참조

: 특정 객체의 인스턴스 메서드 참조

: 객체명::인스턴스메서드명

: ex) person::introduce, person::getName

 

3. 생성자 참조

: 생성자 참조

: 클래스명::new

: ex) Person::new

 

4. 임의 객체의 인스턴스 메서드 참조

: 첫 번째 매개변수가 그 메서드를 호출하는 객체가 됨

: 클래스명::인스턴스메서드명

: Person::introduce, 같은 람다는 (Person p) -> p.introduce()

 

Person.java

package methodref;

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 getName() {
        return name;
    }

    // 인스턴스 메서드
    public String introduce() {
        return "I am " + name;
    }

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

 

MethodRefEx1.java

: 메서드 참조 유형 1~3번에 대해 알아보자

package methodref;

import java.util.function.Supplier;

public class MethodRefEx1 {
    public static void main(String[] args) {
        // 1. 정적 메서드 참조
        Supplier<String> staticMethod1 = () -> Person.greeting();
        System.out.println(staticMethod1.get()); // Hello 출력

        Supplier<String> staticMethod2 = Person::greeting; // 클래스::정적메서드
        System.out.println(staticMethod2.get()); // Hello 출력

        // 2. 특정 객체으 인스턴스 참조
        Person person = new Person("coco");
        Supplier<String> instanceMethod1 = () -> person.introduce();
        System.out.println(instanceMethod1.get()); // I am coco 출력

        Supplier<String> instanceMethod2 = person::introduce; // 객체::인스턴스메서드
        System.out.println(instanceMethod2.get()); // I am coco 출력

        // 3. 생성자 참조
        Supplier<Person> newPerson1 = () -> new Person();
        System.out.println(newPerson1.get()); // methodref.Person@4411d970 출력

        Supplier<Person> newPerson2 = Person::new;
        System.out.println(newPerson2.get()); // methodref.Person@4411d970 출력
    }
}

 

* 메서드 참조에서 ()를 사용하지 않는 이유

()는 메서드를 즉시 호출한다는 의미를 가짐, 여기서 ()가 없는 것은 메서드 참조를 하는 시점에는 메서드를 호출하는게 아니라 단순히 메서드의 이름으로 해당 메서드를 참조만 한다는 뜻

-> 해당 메서드의 실제 호출 시점은 함수형 인터페이스를 통해 이후 이루어짐


메서드 참조 2 - 매개변수

MethodRefEx2.java

: 매개변수가 존재하는 경우에 메서드를 어떻게 참조하는지 알아보자

 

package methodref;

import java.util.function.Function;
import java.util.function.Supplier;

public class MethodRefEx2 {
    public static void main(String[] args) {
        // 1. 정적 메서드 참조
        Function<String, String> staticMethod1 = (name) -> Person.greetingWithName(name);
        System.out.println(staticMethod1.apply("coco")); // Hello coco 출력

        Function<String, String> staticMethod2 = Person::greetingWithName; // 클래스::정적메서드
        System.out.println(staticMethod2.apply("coco")); // Hello coco 출력

        // 2. 특정 객체으 인스턴스 참조
        Person person = new Person("coco");
        Function<Integer, String> instanceMethod1 = (n) -> person.introduceWithNumber(n);
        System.out.println(instanceMethod1.apply(1)); // I am coco, my number is 1 출력

        Function<Integer, String> instanceMethod2 = person::introduceWithNumber; // 객체::인스턴스메서드
        System.out.println(instanceMethod2.apply(1)); // I am coco, my number is 1 출력

        // 3. 생성자 참조
        Function<String, Person> newPerson1 = (name) -> new Person(name);
        System.out.println(newPerson1.apply("coco")); // methodref.Person@4411d970 출력

        Function<String, Person> newPerson2 = Person::new;
        System.out.println(newPerson2.apply("coco")); // methodref.Person@4411d970 출력
    }
}

-> 메서드 참조에서는 매개변수를 생략

-> 함수형 인터페이스의 시그니처(매개변수와 반환 타입)가 이미 정해져 있고, 컴파일러가 그 시그니처를 바탕으로 메서드 참조와 연결해 주기 때문에 명시적으로 매개변수를 작성하지 않아도 자동으로 추론되어 호출됨


메서드 참조 3 - 임의 객체의 인스턴스 메서드 참조

마지막 4번째 유형인 '임의 객체의 인스턴스 메서드 참조'에 대해 알아보자

 

MethodRefEx3.java

package methodref;

import java.util.function.Function;

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

        // 람다
        Function<Person, String> func1 = (Person person) -> person.introduce();
        System.out.println("person1.introduce = " + func1.apply(person1));
        System.out.println("person2.introduce = " + func1.apply(person2));
        System.out.println("person2.introduce = " + func1.apply(person3));

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

 

 

 

임의 객체의 인스턴스 메서드 참조

-> 클래스명::인스턴스 메서드

Person::introduce

1. 왼쪽에 지정한 클래스를 람다의 첫 번째 매개변수로 사용한다.
(Person person)

2. 오른쪽에 지정한 '인스턴스 메서드'를 첫 번째 매개변수를 통해 호출한다.
(Person person) -> person.introduce()

 

 

 

정리

1. 정적 메서드 참조 -> 클래스명::클레스메서드

2. 특정 객체의 인스턴스 메서드 참조 -> 객체명::인스턴스메서드

3. 생성자 참조 -> 클래스명::new(Person::new)

4. 임의 객체의 인스턴스 메서드 참조 -> 클래스명::인스턴스메서드

 

2번과 4번의 차이 !!!

2. 특정 객체의 인스턴스 메서드 참조

person::introduce // 메서드 참조: 인스턴스 person을 지정한다.
() -> person.introduce() // 람다: 지정한 person의 인스턴스 메서드를 사용한다.

// 실행 시점: 이미 지정된 인스턴스가 사용된다.
instanceMethod1.get()

-> 특정 객체의 인스턴스 메서드 참조는 선언 시점부터 이미 인스턴스가 지정되어 있음

-> 람다를 실행하는 시점에 인스턴스를 변경할 수 없음

 

4. 임의 객체의 인스턴스 메서드 참조

Person::introduce // 메서드 참조: Person이라는 타입만 지정한다. 어떤 인스턴스가 사용될지는 아직 모른다.
(Person person) -> person.introduce() // 람다: 매개변수로 넘어오는 person 인스턴스의 메서드를 사용

// 실행 시점: 실행 시점에 인스턴스를 외부에서 전달해서 변경할 수 있다.
fun1.apply(person1)
fun1.apply(person2)
fun1.apply(person3)

-> 선언 시점에 호출할 인스턴스를 지정하지 않음

-> 호출 대상을 매개변수로 선언해두고, 실행 시점이 되야 어떤 객체가 호출되는지 알 수 있음


메서드 참조 4 - 활용

MethodRefEx4.java

package methodref;

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

public class MethodRef4 {
    public static void main(String[] args) {
        List<Person> personList = List.of(new Person("kim"), new Person("park"), new Person("lee"));

        List<String> result1 = mapPersonToString(personList, (Person p) -> p.introduce());
        System.out.println(result1); // [I am kim, I am park, I am lee] 출력

        List<String> result2 = mapPersonToString(personList, Person::introduce);
        System.out.println(result2); // // [I am kim, I am park, I am lee] 출력

        List<String> upperResult1 = mapStringToString(result1, s -> s.toUpperCase());
        System.out.println(upperResult1); // [I AM KIM, I AM PARK, I AM LEE] 출력

        List<String> upperResult2 = mapStringToString(result1, String::toUpperCase);
        System.out.println(upperResult2); // [I AM KIM, I AM PARK, I AM LEE] 출력


    }

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

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

-> 임의 객체 메서드 참조를 사용해 코드가 더 간결해지고 의도가 명확히 드러난다.

 

 

 

매개변수가 있을 경우

// 람다 사용
BiFunction<Person, Integer, String> fun1 = (Person p, Integer number) -> p.introduceWithNumber(number);

// 메서드 참조 사용
BiFunction<Person, Integer, String> fun2 = Person::introduceWithNumber;

-> 첫 번째 인자를 호출 대상 객체로,

-> 나머지 인자들은 순서대로 해당 메서드의 매개변수로 전달