[Java] 62. 메서드 참조

2025. 7. 4. 23:53·Java/Modern Java(8~)

 

메서드 참조

#Java/adv3


메서드 참조가 필요한 이유

  • /예제1
    • BinaryOperator<Integer> add1 = (x, y) -> x + y;
    • BinaryOperator<Integer> add2 = (x, y) -> x + y;
    • 동일한 기능을 하는 람다를 여러 번 작성했는데, 코드가 중복되어 있어 유지 보수가 어려울 수 있다.
  • /예제2
    • x + y를 하나의 메서드 add()로 분리하였다. 람다가 add()를 호출하게 된다.
      • (x, y) -> add(x, y)
    • 남은 문제
      • 람다를 작성할 때마다 (x, y)-> add(x, y) 형태의 코드를 반복해서 작성해야 한다.
      • 매개변수를 전달하는 부분이 장황하다. 람다에서 동일한 모양의 매개변수를 add() 메서드에 그대로 전달하고 있다.
  • /예제3: 메서드 참조(Method Reference) 문법를 사용
    • 메서드 참조는 쉽게 말해서, "이미 정의된 메서드를 그대로 참조하여 람다 표현식을 더 간결하게 작성하는 방법"
public class MethodRefStartV3 {

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

        Integer result1 = add1.apply(1, 2);
        System.out.println("result1 = " + result1);

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

    static int add(int x, int y) {
        return x + y;
    }
}
  • 클래스명::메서드명을 사용하여 (x,y) -> add(x,y) 라는 람다를 더욱 간단하게 표현했다.
  • 람다가 하는 일이 메서드 호출하는게 전부니까, 그냥 메서드로 간략하게 적어주는 거다.
  • 메서드 참조의 장점
    • 코드의 간결함, 가독성
      • 매개 변수를 작성하지 않아도, 컴파일러가 알아서 매칭해준다.
    • 별도 로직 분리로 인한 재사용성 향상
  • 메서드 참조는 이미 정의된 메서드를 람다로 변환하여 더욱 간결하게 사용할 수 있도록 해주는 문법적 편의 기능이다.
    • 람다에서 이미 정의된 메서드를 그대로 호출하는 경우에는 메서드 참조를 활용하여 직관성, 간결성, 재사용성을 챙겨보자~

메서드 참조1 - 시작

  • 메서드 참조(Method Reference)는 "이미 정의된 메서드를 람다처럼 간결하게 표현" 할 수 있게 해주는 문법
  • 람다 내부에서 단순히 어떤 메서드(정적/인스턴스/생성자 등)를 호출만하고 있을 경우, 메서드 참조를 사용할 수 있다.
    • 람다와 메서드 참조는 동등하게 동작한다. 단순히 축약해주는 문법일 뿐이다.
(x, y) -> 클래스명.메서드명(x, y) // 기존 람다
클래스명::메서드명 // 메서드 참조

메서드 참조의 4가지 유형

  1. 정적 메서드 참조
  • 설명: 이름 그대로 정적(static) 메서드를 참조한다.
  • 문법: 클래스명::메서드명
  • 예: Math::max, Integer::parseInt 등

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

  • 설명: 이름 그대로 특정 객체의 인스턴스 메서드를 참조한다.
  • 문법: 객체명::인스턴스메서드명
  • 예: person::introduce, person::getName 등

3. 생성자 참조

  • 설명: 이름 그대로 생성자를 참조한다.
  • 문법: 클래스명::new
  • 예: Person::new

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

  • 설명: 첫 번째 매개변수(또는 해당 람다가 받을 대상)가 그 메서드를 호출하는 객체가 된다.
  • 문법: 클래스명::인스턴스메서드명
  • 예: Person::introduce, 같은 람다: (Person p) -> p.introduce()

예제1

아래 클래스를 메서드 참조를 통해 호출해보자.

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

매개변수가 없는 메서드를 메서드 참조를 통해 호출

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

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

  • 참고로 메서드 참조의 문법을 잘 보면 뒤에 메서드 명 뒤에 ()가 없다.
    • 예를 들어서 Person::greeting()이 아니라, Person::greeting 으로 표현한다.
  • ()는 메서드를 즉시 호출한다는 의미를 가진다. 여기서 ()가 없는 것은 메서드 참조를 하는 시점에는 메서드를 호출하는게 아니라 단순히 메서드의 이름으로 해당 메서드를 참조만 한다는 뜻이다.
    • 해당 메서드의 실제 호출 시점은 함수형 인터페이스를 통해서 이후에 이루어진다.

메서드 참조2 - 매개변수1: 매개변수가 있는 경우

// 매개변수 추가
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"));
    }
}
  • 메서드 참조의 경우 매개변수를 생략한다. 매개변수가 여러개라면 순서대로 전달된다.
  • 메서드 참조에서 매개변수를 생략하는 이유: 함수형 인터페이스의 시그니처(매개변수와 반환 타입)가 이미 정해져 있고, 컴파일러가 그 시그니처를 바탕으로 메서드 참조와 연결해 주기 때문에 명시적으로 매개변수를 작성하지 않아도 자동으로 추론되어 호출된다.
  • 예를 들어, Function<String, String> 이라는 함수형 인터페이스가
    • 입력: String
    • 출력: String
  • 이라는 시그니처를 갖고 있기 때문에, Person::greetingWithName 를 보면 컴파일러가 "이 Function<String, String> 을 만족시키려면 greetingWithName(String name) 을 호출하면 되겠구나"하고 자동으로 판단해 매개변수를 전달한다.

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

  • 객체 인스턴스를 픽스해두고 그 인스턴스의 메서드를 호출하는게 아니라, 클래스에 있는 인스턴스 메서드를 픽스해두고, 객체를 바꿔가며 호출한다.
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> fun1 = (Person person) -> person.introduce();
		// person -> person.introduce(); 도 가능
        System.out.println("person1.introduce = " + fun1.apply(person1));
        System.out.println("person2.introduce = " + fun1.apply(person2));
        System.out.println("person3.introduce = " + fun1.apply(person3));

        // 메서드 참조, 타입이 첫 번째 매개변수가 됨, 그리고 첫 번째 매개변수의 메서드를 호출, 나머지는 순서대로 매개변수에 전달
        Function<Person, String> fun2 = Person::introduce; // 타입::인스턴스메서드
        System.out.println("person1.introduce = " + fun2.apply(person1));
        System.out.println("person2.introduce = " + fun2.apply(person2));
        System.out.println("person3.introduce = " + fun2.apply(person3));
    }
}
  • 코드에서 보면 apply() 에 person1, person2, person3 을 각각 전달한 것을 확인할 수 있다.
    • person1 을 람다에 전달하면 person1.introduce() 가 호출된다.
    • person2 를 람다에 전달하면 person2.introduce() 가 호출된다.
    • person3 을 람다에 전달하면 person3.introduce() 가 호출된다.
  • 이 람다는 매개변수로 지정한 특정 타입의 객체에 대해 동일한 메서드를 호출하는 패턴을 보인다.
    • 타입: Person
    • 메서드: introduce()
  • 특정 타입의 임의 객체의 인스턴스 메서드를 참조한다.
    • 매개변수로 지정한 특정 타입: Person
    • 임의 객체: person1, person2, person3, 또는 Person 타입을 구현한 어떠한 객체든 해당 람다에 전달할 수 있음
    • 인스턴스 메서드: introduce()
  • 특정 타입의 임의 객체에 대해 동일한 인스턴스 메서드를 호출하는 패턴을 메서드 참조로 손쉽게 표현할 수 있다.
    • 타입::인스턴스메서드
    • Person::introduce()

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

  • (Reference to an instance method of an arbitrary object of a particular type)
  • 이런 메서드 참조를 특정 타입의 임의 객체의 인스턴스 참조라 한다.
  • Person::introduce 와 같이 선언하면 다음과 같은 람다가 된다.
    Person::introduce
    1. 왼쪽에 지정한 클래스를 람다의 첫 번째 매개변수로 사용한다.(매개변수를 만듬)
       (Person person)
    
    2. 오른쪽에 지정한 '인스턴스 메서드'를 첫 번째 매개변수를 통해 호출한다.
       (Person person) -> person.introduce()
    
  • 특정 객체의 인스턴스 메서드 참조와, 임의 객체의 인스턴스 메서드 참조는 완전히 다르다.
    • 특정 객체의 인스턴스 메서드 참조: 객체명::인스턴스메서드 (person::introduce)
      • 객체 인스턴스와 인스턴스메서드를 지정해둬야 한다.
    • 임의 객체의 인스턴스 메서드 참조: 클래스명::인스턴스메서드 (Person::introduce)
      • 타입과 인스턴스 메서드만 지정해두면 된다. 객체 인스턴스는 지정하지 않고, 호출 시점에 인스턴스 참조를 전달받는다.
  • 사실 메서드 참조 중에는 이 기능이 가장 많이 사용된다.

메서드 참조4 - 활용1: 임의 객체의 인스턴스 메서드 참조를 활용해보자.

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

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

    static List<String> mapStringToString(List<String> strings, Function<String, String> fun) {
        List<String> result = new ArrayList<>();
        for (String s : strings) {
            String applied = fun.apply(s);
            result.add(applied);
        }
        return result;
    }
  • 여기서는 임의 객체의 인스턴스 메서드 참조를 사용했다.
  • Person::introduce 는 클래스::인스턴스메서드 이다. 이런 형식이 나오면 임의 객체의 인스턴스 메서드 참조로 이해하면 된다.
  • 따라서 왼쪽에 지정한 Person 이 첫 번째 매개변수가 되고, 오른쪽에 지정한 인스턴스 메서드를 해당 매개변수가 호출한다.
    • 위의 람다와 같은 형식이다.
  • fun.apply(p) 메서드에 new Person("Kim"), new Person("Park"), new Person("Lee") 각각의 인스턴스가 인자로 넘어가면서 해당 인스턴스의 introduce() 메서드가 호출된다.

메서드 참조5 - 활용2: 스트림과 조합해서 메서드 참조를 활용해보자.

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

        List<String> result1 = MyStreamV3.of(personList)
                .map(person -> person.introduce())
                .map(str -> str.toUpperCase())
                .toList();
        System.out.println("result1 = " + result1);

        List<String> result2 = MyStreamV3.of(personList)
                .map(Person::introduce) // person -> person.introduce()
                .map(String::toUpperCase) // (String name) -> name.toUpperCase()
                .toList();
        System.out.println("result2 = " + result2);
    }
}
  • 메서드 참조는 람다보다 타입이 직관적이라는 장점이 있음.
    • person person.introduce()에서 person 타입을 바로 확인하지 못하지만, Person::introduce는 타입이 바로 드러남
  • 메서드 참조를 사용하면 람다 표현식을 더욱 직관적으로 표현할 수 있으며, 각 처리 단계에서 호출되는 메서드가 무엇인지 쉽게 파악할 수 있다. 메서드 참조에 익숙해지자.

메서드 참조6 - 매개변수2: 임의 객체의 인스턴스 메서드 참조에서 매개변수가 늘어나면?

// 매개변수 추가
public class MethodRefEx6 {
    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));
    }
}
  • fun1 에서는 람다를 사용하여 p.introduceWithNumber(number) 를 호출한다.
  • fun2 에서는 Person::introduceWithNumber 라는 메서드 참조를 사용한다.
  • 첫 번째 매개변수(Person)가 메서드를 호출하는 객체가 되고, 두 번째 매개변수(Integer)가 introduceWithNumber() 의 실제 인자로 전달된다. 첫 번째 이후의 매개변수는 모두 순서대로 실제 인자로 전달된다.


이처럼 임의 객체의 인스턴스 메서드 참조는 함수형 인터페이스의 시그니처에 따라

  • 첫 번째 인자를 호출 대상 객체로
  • 나머지 인자들은 순서대로 해당 메서드의 매개변수로 전달한다.

Ref) 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 강의 | 김영한 - 인프런

'Java > Modern Java(8~)' 카테고리의 다른 글

[Java] 64. 스트림 API2 - 기능  (0) 2025.07.04
[Java] 63. 스트림 API1 - 기본  (0) 2025.07.04
[Java] 61. 람다 vs 익명 클래스  (0) 2025.07.04
[Java] 60. 람다 활용  (0) 2025.07.04
[Java] 59. 함수형 인터페이스  (0) 2025.07.04
'Java/Modern Java(8~)' 카테고리의 다른 글
  • [Java] 64. 스트림 API2 - 기능
  • [Java] 63. 스트림 API1 - 기본
  • [Java] 61. 람다 vs 익명 클래스
  • [Java] 60. 람다 활용
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (457)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (130)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (6)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Java] 62. 메서드 참조
상단으로

티스토리툴바