메서드 참조
#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가지 유형
- 정적 메서드 참조
- 설명: 이름 그대로 정적(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()
의 실제 인자로 전달된다. 첫 번째 이후의 매개변수는 모두 순서대로 실제 인자로 전달된다.
이처럼 임의 객체의 인스턴스 메서드 참조는 함수형 인터페이스의 시그니처에 따라
- 첫 번째 인자를 호출 대상 객체로
- 나머지 인자들은 순서대로 해당 메서드의 매개변수로 전달한다.
'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 |