[Java 개념노트 57] 메서드 참조 이해하기
안녕하세요. Java 개념노트 시리즈 쉰일곱 번째 글입니다.
지난 글에서는 람다식과 함께 자주 사용하는 함수형 인터페이스에 대해 정리했습니다. 이번 글에서는 람다식을 더 짧고 읽기 쉽게 표현할 수 있는 메서드 참조에 대해 알아보겠습니다.
람다식을 사용하면 코드를 짧게 만들 수 있습니다. 그런데 람다식 안에서 이미 존재하는 메서드를 그대로 호출만 하는 경우가 있습니다. 이럴 때는 :: 문법을 사용해서 더 간단하게 표현할 수 있습니다. 이것이 메서드 참조입니다.
1. 메서드 참조란?
메서드 참조는 람다식이 단순히 기존 메서드를 호출하는 형태일 때, 그 람다식을 더 짧게 표현하는 문법입니다.
메서드 참조는 :: 기호를 사용합니다.
클래스명::메서드명
객체명::메서드명
클래스명::new
쉽게 말하면
메서드 참조는 람다식 안에서 “이 메서드 그대로 실행해 주세요”라고 짧게 적는 문법입니다.
2. 람다식과 메서드 참조 비교
가장 대표적인 예로 문자열을 출력하는 코드를 보겠습니다.
람다식
Consumer<String> printer = message -> System.out.println(message);
메서드 참조
Consumer<String> printer = System.out::println;
두 코드는 비슷한 의미입니다. 람다식은 message를 받아서 System.out.println(message)를 호출합니다. 메서드 참조는 이 흐름을 System.out::println으로 줄여 쓴 것입니다.
핵심 포인트
람다식이 이미 존재하는 메서드를 그대로 호출하기만 한다면 메서드 참조로 줄일 수 있습니다.
3. 메서드 참조 기본 예제
Consumer와 System.out::println을 사용해보겠습니다.
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> printer = System.out::println;
printer.accept("Java Method Reference");
}
}
실행 결과는 다음과 같습니다.
Java Method Reference
printer.accept()를 호출하면 전달한 값이 System.out.println()으로 출력됩니다.
4. 메서드 참조는 함수형 인터페이스와 함께 사용된다
람다식처럼 메서드 참조도 혼자 단독으로 쓰는 문법이 아닙니다. 메서드 참조는 함수형 인터페이스 타입에 대입하거나, 함수형 인터페이스가 필요한 위치에서 사용합니다.
Consumer<String> printer = System.out::println;
위 코드에서 Consumer<String>의 추상 메서드는 accept(String value)입니다. System.out.println(String value)와 형태가 맞기 때문에 메서드 참조로 연결할 수 있습니다.
5. 메서드 참조의 종류
메서드 참조는 크게 다음과 같은 형태로 나눌 수 있습니다.
| 종류 | 형태 | 예시 |
| 정적 메서드 참조 | 클래스명::정적메서드명 |
Integer::parseInt |
| 특정 객체의 인스턴스 메서드 참조 | 객체명::메서드명 |
System.out::println |
| 특정 타입의 인스턴스 메서드 참조 | 클래스명::인스턴스메서드명 |
String::length |
| 생성자 참조 | 클래스명::new |
Member::new |
6. 정적 메서드 참조
정적 메서드는 객체를 만들지 않고 클래스 이름으로 호출하는 메서드입니다. 정적 메서드 참조는 클래스명::정적메서드명 형태로 작성합니다.
클래스명::정적메서드명
예를 들어 Integer.parseInt()는 문자열을 정수로 바꾸는 정적 메서드입니다.
람다식
Function<String, Integer> parser = text -> Integer.parseInt(text);
메서드 참조
Function<String, Integer> parser = Integer::parseInt;
람다식이 받은 값을 그대로 Integer.parseInt()에 넘기고 있으므로 메서드 참조로 줄일 수 있습니다.
7. 정적 메서드 참조 예제
문자열 숫자를 정수로 변환하는 예제를 보겠습니다.
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> parser = Integer::parseInt;
Integer number = parser.apply("100");
System.out.println(number + 20);
}
}
실행 결과는 다음과 같습니다.
120
parser.apply("100")을 호출하면 내부적으로 Integer.parseInt("100")이 실행됩니다.
8. 특정 객체의 인스턴스 메서드 참조
이미 만들어진 객체의 메서드를 참조할 수도 있습니다. 이때는 객체명::메서드명 형태를 사용합니다.
객체명::메서드명
대표적인 예가 System.out::println입니다. 여기서 System.out은 이미 존재하는 객체이고, println은 그 객체의 메서드입니다.
람다식
Consumer<String> printer = text -> System.out.println(text);
메서드 참조
Consumer<String> printer = System.out::println;
9. 특정 객체의 인스턴스 메서드 참조 예제
직접 만든 객체의 메서드를 참조해보겠습니다.
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
MessagePrinter messagePrinter = new MessagePrinter();
Consumer<String> printer = messagePrinter::print;
printer.accept("Java");
printer.accept("Spring");
}
}
class MessagePrinter {
public void print(String message) {
System.out.println("메시지: " + message);
}
}
실행 결과는 다음과 같습니다.
메시지: Java
메시지: Spring
messagePrinter::print는 messagePrinter 객체의 print() 메서드를 참조합니다.
10. 특정 타입의 인스턴스 메서드 참조
조금 헷갈릴 수 있는 형태가 클래스명::인스턴스메서드명입니다.
클래스명::인스턴스메서드명
예를 들어 문자열의 길이를 구하는 코드를 보겠습니다.
람다식
Function<String, Integer> lengthFunction = text -> text.length();
메서드 참조
Function<String, Integer> lengthFunction = String::length;
String::length는 전달된 String 객체의 length() 메서드를 호출한다는 의미입니다.
11. String::length 예제
문자열 길이를 구하는 예제를 보겠습니다.
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> lengthFunction = String::length;
System.out.println(lengthFunction.apply("Java"));
System.out.println(lengthFunction.apply("Spring"));
}
}
실행 결과는 다음과 같습니다.
4
6
lengthFunction.apply("Java")를 호출하면 "Java".length()가 실행되는 것처럼 동작합니다.
12. 특정 타입의 인스턴스 메서드 참조가 헷갈리는 이유
String::length는 정적 메서드처럼 보이지만, 실제로는 String 객체의 인스턴스 메서드인 length()를 참조합니다.
Function<String, Integer> lengthFunction = String::length;
lengthFunction.apply("Java")
↓
"Java".length()
↓
4
즉, 전달된 첫 번째 값이 메서드를 호출하는 대상 객체가 됩니다.
13. Comparator와 메서드 참조
문자열을 대소문자를 무시하고 정렬할 때 String::compareToIgnoreCase를 사용할 수 있습니다.
람다식
Collections.sort(words, (a, b) -> a.compareToIgnoreCase(b));
메서드 참조
Collections.sort(words, String::compareToIgnoreCase);
(a, b) -> a.compareToIgnoreCase(b)는 첫 번째 문자열 a의 메서드에 두 번째 문자열 b를 전달합니다. 이런 형태도 String::compareToIgnoreCase로 줄일 수 있습니다.
14. Comparator 메서드 참조 예제
문자열 목록을 대소문자 구분 없이 정렬해보겠습니다.
import java.util.ArrayList;
import java.util.Collections;
public class Main {
public static void main(String[] args) {
ArrayList<String> words = new ArrayList<>();
words.add("spring");
words.add("Java");
words.add("sql");
Collections.sort(words, String::compareToIgnoreCase);
System.out.println(words);
}
}
실행 결과는 다음과 같습니다.
[Java, spring, sql]
String::compareToIgnoreCase가 정렬 기준 역할을 했습니다.
15. 생성자 참조란?
생성자 참조는 객체를 생성하는 코드를 짧게 표현하는 문법입니다.
형태는 다음과 같습니다.
클래스명::new
예를 들어 Member 객체를 만드는 람다식은 생성자 참조로 줄일 수 있습니다.
람다식
Supplier<Member> supplier = () -> new Member();
생성자 참조
Supplier<Member> supplier = Member::new;
16. 기본 생성자 참조 예제
매개변수가 없는 기본 생성자를 참조해보겠습니다.
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier<Member> supplier = Member::new;
Member member = supplier.get();
member.printInfo();
}
}
class Member {
public void printInfo() {
System.out.println("새 회원 객체가 생성되었습니다.");
}
}
실행 결과는 다음과 같습니다.
새 회원 객체가 생성되었습니다.
supplier.get()을 호출하면 new Member()가 실행됩니다.
17. 매개변수가 있는 생성자 참조
생성자에 매개변수가 있다면 Function을 사용할 수 있습니다.
람다식
Function<String, Member> creator = name -> new Member(name);
생성자 참조
Function<String, Member> creator = Member::new;
Function<String, Member>는 String을 받아 Member를 반환하는 구조입니다. 따라서 Member(String name) 생성자와 연결될 수 있습니다.
18. 매개변수가 있는 생성자 참조 예제
회원 이름을 받아 Member 객체를 생성해보겠습니다.
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Member> creator = Member::new;
Member member = creator.apply("건");
member.printInfo();
}
}
class Member {
private String name;
public Member(String name) {
this.name = name;
}
public void printInfo() {
System.out.println("회원 이름: " + name);
}
}
실행 결과는 다음과 같습니다.
회원 이름: 건
creator.apply("건")을 호출하면 new Member("건")이 실행됩니다.
19. 매개변수가 두 개인 생성자 참조
생성자 매개변수가 두 개라면 BiFunction을 사용할 수 있습니다.
import java.util.function.BiFunction;
public class Main {
public static void main(String[] args) {
BiFunction<String, Integer, Product> creator = Product::new;
Product product = creator.apply("키보드", 50000);
product.printInfo();
}
}
class Product {
private String name;
private int price;
public Product(String name, Integer price) {
this.name = name;
this.price = price;
}
public void printInfo() {
System.out.println(name + ": " + price + "원");
}
}
실행 결과는 다음과 같습니다.
키보드: 50000원
BiFunction<String, Integer, Product>는 String과 Integer를 받아 Product를 반환합니다. 그래서 Product(String name, Integer price) 생성자와 연결됩니다.
20. 컬렉션 forEach에서 메서드 참조 사용하기
컬렉션을 반복하며 값을 출력할 때 메서드 참조를 자주 사용합니다.
람다식
list.forEach(item -> System.out.println(item));
메서드 참조
list.forEach(System.out::println);
값을 받아 그대로 출력하는 단순한 형태라면 메서드 참조가 더 깔끔합니다.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
list.add("SQL");
list.forEach(System.out::println);
}
}
실행 결과는 다음과 같습니다.
Java
Spring
SQL
21. 메서드 참조가 가능한 조건
메서드 참조는 아무 메서드나 마음대로 연결할 수 있는 것이 아닙니다. 함수형 인터페이스의 추상 메서드 형태와 참조하려는 메서드의 형태가 맞아야 합니다.
| 함수형 인터페이스 | 필요한 형태 | 가능한 메서드 참조 |
Consumer<String> |
String을 받고 반환 없음 | System.out::println |
Function<String, Integer> |
String을 받고 Integer 반환 | Integer::parseInt, String::length |
Supplier<Member> |
입력 없이 Member 반환 | Member::new |
BiFunction<String, Integer, Product> |
String, Integer를 받고 Product 반환 | Product::new |
매개변수 개수, 매개변수 타입, 반환 타입의 흐름이 맞아야 메서드 참조를 사용할 수 있습니다.
22. 람다식이 더 나은 경우도 있다
메서드 참조는 짧고 깔끔하지만, 모든 상황에서 좋은 것은 아닙니다. 간단히 기존 메서드를 호출하는 경우에는 좋지만, 추가 로직이 있으면 람다식이 더 명확할 수 있습니다.
메서드 참조가 좋은 경우
list.forEach(System.out::println);
람다식이 더 나은 경우
list.forEach(item -> {
System.out.println("과목명: " + item);
});
두 번째 코드는 단순히 출력만 하는 것이 아니라 문구를 붙이고 있습니다. 이런 경우에는 람다식이 더 읽기 쉽습니다.
기억하기
메서드 참조는 람다식을 무조건 대체하는 문법이 아닙니다. 기존 메서드를 그대로 호출하는 단순한 람다식일 때 사용하면 좋습니다.
23. 메서드 참조 전체 예제
이번에는 정적 메서드 참조, 인스턴스 메서드 참조, 생성자 참조를 함께 사용하는 예제를 보겠습니다.
import java.util.ArrayList;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Function<String, Integer> parser = Integer::parseInt;
Function<String, Integer> lengthFunction = String::length;
Consumer<String> printer = System.out::println;
Supplier<Member> memberSupplier = Member::new;
Integer number = parser.apply("100");
Integer length = lengthFunction.apply("Spring");
printer.accept("숫자 변환 결과: " + number);
printer.accept("문자열 길이: " + length);
Member member = memberSupplier.get();
member.printInfo();
ArrayList<String> subjects = new ArrayList<>();
subjects.add("Java");
subjects.add("Spring");
subjects.add("SQL");
subjects.forEach(System.out::println);
}
}
class Member {
public void printInfo() {
System.out.println("Member 객체 생성 완료");
}
}
실행 결과는 다음과 같습니다.
숫자 변환 결과: 100
문자열 길이: 6
Member 객체 생성 완료
Java
Spring
SQL
24. 메서드 참조 사용 시 자주 하는 실수
메서드 참조를 처음 배울 때는 아래와 같은 실수를 자주 합니다.
| 실수 | 문제점 | 해결 방법 |
| 람다식이 단순 호출이 아닌데 메서드 참조로 바꾸려 함 | 추가 로직이 있으면 표현이 맞지 않음 | 기존 메서드 호출만 할 때 메서드 참조 사용 |
| 함수형 인터페이스 형태와 메서드 형태가 맞지 않음 | 매개변수나 반환 타입이 맞지 않아 오류 발생 | 입력과 반환 타입을 먼저 확인 |
String::length를 정적 메서드라고 착각 |
실제로는 전달된 String 객체의 인스턴스 메서드 호출 | text -> text.length()의 축약으로 이해 |
| 생성자 참조의 입력 개수와 생성자 매개변수 불일치 | 연결할 생성자를 찾지 못함 | Supplier, Function, BiFunction 형태 확인 |
| 메서드 참조를 무조건 더 좋은 문법이라고 생각 | 코드 의도가 오히려 덜 보일 수 있음 | 읽기 쉬운 쪽을 선택 |
25. 직접 연습해보기
아래 코드를 직접 작성하고 실행해보세요.
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Function<String, Integer> parser = Integer::parseInt;
Function<String, Integer> lengthFunction = String::length;
Consumer<String> printer = System.out::println;
BiFunction<String, Integer, Product> productCreator = Product::new;
Integer price = parser.apply("50000");
Product product = productCreator.apply("키보드", price);
product.printInfo();
printer.accept("상품명 길이: " + lengthFunction.apply("키보드"));
ArrayList<String> words = new ArrayList<>();
words.add("spring");
words.add("Java");
words.add("sql");
Collections.sort(words, String::compareToIgnoreCase);
words.forEach(System.out::println);
}
}
class Product {
private String name;
private Integer price;
public Product(String name, Integer price) {
this.name = name;
this.price = price;
}
public void printInfo() {
System.out.println(name + ": " + price + "원");
}
}
실행 결과는 다음과 같습니다.
키보드: 50000원
상품명 길이: 3
Java
spring
sql
26. 이번 글 정리
이번 글에서는 람다식을 더 간결하게 표현하는 메서드 참조에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.
- 메서드 참조는 람다식이 기존 메서드를 그대로 호출할 때 더 짧게 표현하는 문법이다.
- 메서드 참조는
::기호를 사용한다. System.out::println은message -> System.out.println(message)를 줄인 형태로 볼 수 있다.- 정적 메서드 참조는
클래스명::정적메서드명형태로 작성한다. Integer::parseInt는 문자열을 정수로 바꾸는 메서드 참조이다.- 특정 객체의 인스턴스 메서드 참조는
객체명::메서드명형태로 작성한다. - 특정 타입의 인스턴스 메서드 참조는
클래스명::인스턴스메서드명형태로 작성한다. String::length는 전달된 String 객체의length()를 호출하는 형태이다.- 생성자 참조는
클래스명::new형태로 작성한다. - 생성자 참조는
Supplier,Function,BiFunction등과 함께 사용할 수 있다. - 메서드 참조는 함수형 인터페이스의 메서드 형태와 참조할 메서드 형태가 맞아야 사용할 수 있다.
- 추가 로직이 필요한 경우에는 메서드 참조보다 람다식이 더 읽기 쉬울 수 있다.
한 줄 요약
메서드 참조는 람다식이 기존 메서드를 그대로 호출하는 경우 ::를 사용해서 더 짧고 간결하게 표현하는 문법입니다.
다음 글 예고
다음 글에서는 [Java 개념노트 58] Stream API란 무엇인가?라는 주제로 컬렉션 데이터를 필터링하고 변환하고 집계하는 흐름을 더 선언적으로 작성하는 방법을 정리해보겠습니다.
GWDEVELBlog Java 개념노트 시리즈

'Computer Science > Java' 카테고리의 다른 글
| [Java 개념노트 60] Optional 클래스 이해하기 (0) | 2026.07.20 |
|---|---|
| [Java 개념노트 59] Stream 중간 연산과 최종 연산 자세히 보기 (1) | 2026.07.18 |
| [Java ] 개념노트 56 함수형 인터페이스 이해하기 (0) | 2026.06.18 |
| [Java ] 개념노트 55 람다식이란 무엇인가 (0) | 2026.06.18 |
| [Java ] 개념노트 54 제한된 제네릭과 와일드카드 이해하기 (0) | 2026.06.18 |