💻 Java 메서드 참조
Java에서 메서드 참조(Method Reference)는 람다 표현식을 간결하게 표현할 수 있는 방법입니다. 메서드 참조는 코드의 가독성을 높이고, 불필요한 중복을 줄이는 데 유용하죠. 이번 포스트에서는 메서드 참조의 유형, 메서드 참조의 동작 방식, 그리고 특정 객체의 메서드 참조 사용에 대해 상세하게 설명드리겠습니다.
🔹 메서드 참조의 유형
메서드 참조는 네 가지 주요 유형으로 나눌 수 있습니다:
- 정적 메서드 참조 (Static Method Reference)
- 인스턴스 메서드 참조 (Instance Method Reference)
- 특정 객체의 인스턴스 메서드 참조 (Reference to an instance method of a particular object)
- 생성자 참조 (Constructor Reference)
이제 각 유형이 어떻게 동작하는지 자세히 알아보겠습니다.
Integer::parseInt와 String::toUpperCase
먼저, 정적 메서드 참조와 인스턴스 메서드 참조를 살펴보겠습니다.
Integer::parseInt
: 이 메서드 참조는Integer
클래스의 정적 메서드인parseInt
를 참조합니다.parseInt
는 문자열을 정수로 변환하는 작업을 수행합니다.
Function<String, Integer> parseFunction = Integer::parseInt;
Integer result = parseFunction.apply("123");
System.out.println(result); // 123
여기서 Integer::parseInt
는 Function<String, Integer>
인터페이스와 매핑됩니다. 이는 입력된 String
을 Integer
로 변환하는 기능을 합니다. apply
메서드를 호출하면 parseInt
메서드가 실행되어 문자열 "123"
이 정수 123
으로 변환됩니다.
String::toUpperCase
: 이 메서드 참조는String
클래스의 인스턴스 메서드인toUpperCase
를 참조합니다. 이 메서드는 문자열을 대문자로 변환합니다.
Function<String, String> upperCaseFunction = String::toUpperCase;
String result = upperCaseFunction.apply("hello");
System.out.println(result); // HELLO
여기서 String::toUpperCase
는 Function<String, String>
과 매핑되어, 입력된 문자열을 대문자로 변환합니다. 이 메서드 참조는 어떤 특정 인스턴스에 묶여 있지 않으며, 입력되는 문자열의 toUpperCase
메서드를 호출하게 됩니다.
🔹 정적 메서드 참조, 인스턴스 메서드 참조, 생성자 참조의 차이점
이제 메서드 참조의 유형별 차이점을 살펴보겠습니다.
- 정적 메서드 참조 (ClassName::methodName): 클래스의 정적 메서드를 참조합니다. 이 참조는 클래스 자체에 대해 정적으로 정의된 메서드를 대상으로 합니다.
Function<String, Integer> parseInt = Integer::parseInt;
여기서 Integer::parseInt
는 Integer
클래스에 정적으로 정의된 parseInt
메서드를 참조합니다. 이 메서드는 String
을 받아서 Integer
로 변환합니다.
- 인스턴스 메서드 참조 (ClassName::methodName): 특정 인스턴스에 의존하지 않고, 전달된 인스턴스의 메서드를 참조합니다. 이 참조는 인스턴스 메서드이지만, 클래스 이름으로 참조되며, 실제로는 메서드가 호출될 때 전달된 객체의 메서드를 실행합니다.
Function<String, String> upperCase = String::toUpperCase;
String::toUpperCase
는 전달된 String
객체의 toUpperCase
메서드를 호출합니다. 여기서 upperCase
함수는 입력된 문자열을 대문자로 변환하는 역할을 합니다.
- 특정 객체의 인스턴스 메서드 참조 (instance::methodName): 특정 객체의 메서드를 참조합니다. 이 유형의 메서드 참조는 미리 정의된 객체의 메서드를 대상으로 합니다.
String prefix = "Hello, ";
Function<String, String> greeting = prefix::concat;
String result = greeting.apply("World");
System.out.println(result); // Hello, World
여기서 prefix::concat
은 prefix
라는 특정 String
객체의 concat
메서드를 참조합니다. 이 메서드 참조는 greeting.apply("World")
가 호출될 때 prefix.concat("World")
가 실행되는 방식으로 동작합니다.
- 생성자 참조 (ClassName::new): 클래스의 생성자를 참조하여 새로운 객체를 만듭니다. 이 참조는 기본 생성자 또는 매개변수가 있는 생성자와 연결될 수 있습니다.
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
ArrayList::new
는 ArrayList
의 기본 생성자를 참조하며, get()
메서드를 호출하면 새로운 ArrayList
인스턴스가 생성됩니다. Supplier
인터페이스와 함께 사용되어, new ArrayList<>()
와 동일한 효과를 제공합니다.
🔹 메서드 참조의 동작 방식
메서드 참조는 람다 표현식의 간결한 대체물로 작동하며, Java 컴파일러는 이를 분석하여 적절한 메서드 또는 생성자를 호출합니다. 컴파일러가 어떻게 메서드 참조를 해석하고 작동하는지 이해해 봅시다.
- 정적 메서드 참조: 컴파일러는 클래스명과 메서드명이 명시된 경우 이를 정적 메서드로 인식합니다. 예를 들어
Integer::parseInt
는Integer
클래스의 정적 메서드인parseInt
로 매핑됩니다.이 경우parseInt
는 특정 인스턴스 없이 호출될 수 있습니다. 컴파일러는Integer::parseInt
가Function<String, Integer>
과 같은 인터페이스에 맞는 메서드라는 점을 인식하고 이를 사용합니다. Function<String, Integer> parseInt = Integer::parseInt;
- 인스턴스 메서드 참조: 클래스명만 지정된 경우, 메서드 참조는 전달된 객체의 인스턴스 메서드를 참조하는 것으로 해석됩니다. 이 경우, 컴파일러는 메서드 참조가 특정 객체의 인스턴스 메서드로 적용될 수 있는지를 판단합니다.컴파일러는
String::toUpperCase
가String
객체에서 호출 가능한 인스턴스 메서드라는 점을 인식합니다. Function<String, String> upperCase = String::toUpperCase;
- 특정 객체의 인스턴스 메서드 참조: 특정 객체가 주어지고 그 객체의 메서드가 참조되는 경우, 해당 객체의 메서드가 참조됩니다. 이는 메서드 참조가 어떤 객체에 속한 메서드인지를 컴파일러가 직접 판단하게 됩니다.컴파일러는
greeting::concat
이 특정greeting
객체의concat
메서드를 참조한다는 것을 인식합니다. 이 참조는concatFunction.apply("World")
가 호출될 때greeting.concat("World")
를 수행하게 됩니다. String greeting = "Hello, "; Function<String, String> concatFunction = greeting::concat;
- 생성자 참조: 클래스명과
new
키워드가 지정된 경우, 이를 생성자 참조로 인식합니다. 컴파일러는 생성자가 호출될 때 어떤 매개변수가 필요한지를 확인하고, 적절한 생성자를 선택합니다.여기서ArrayList::new
는ArrayList
의 기본 생성자를 참조하며, 컴파일러는Supplier<List<String>>
인터페이스에 맞게 이 참조를 사용합니다. Supplier<List<String>> listSupplier = ArrayList::new;
🔹 특정 객체의 메서드 참조
특정 객체의 인스턴스 메서드를 Stream에서 메서드 참조로 사용할 수 있습니다. 이는 특정 객체를 대상으로 메서드를 호출해야 하는 경우 유용하게 사용됩니다. 이 경우 메서드 참조는 주로 람다 표현식의 대안으로 사용되며, 간결하고 가독성 높은 코드를 작성할 수 있게 해줍니다.
예시:
List<String> messages = Arrays.asList("Hello", "World");
String prefix = "Message: ";
messages.stream()
.map(prefix::concat) // prefix 객체의 concat 메서드 참조
.forEach(System.out::println);
// 출력: Message: Hello, Message: World
이 예제에서는 prefix::concat
을 사용하여 Stream의 각 요소에 대해 concat
메서드를 호출합니다. prefix
객체에 대해 concat
메서드가 호출되어, 각 메시지에 "Message: "라는 접두어가 추가됩니다. 이처럼 특정 객체의 메서드 참조는 Stream API와 결합하여 매우 강력한 기능을 발휘할 수 있습니다.
💡 정리
- 메서드 참조 유형: 정적 메서드 참조, 인스턴스 메서드 참조, 특정 객체의 인스턴스 메서드 참조, 생성자 참조의 네 가지 주요 유형이 있습니다.
- 메서드 참조 동작 방식: Java 컴파일러는 메서드 참조를 분석하여, 적절한 메서드나 생성자를 자동으로 선택하고 실행합니다. 이 과정에서 메서드 참조가 어떤 유형에 속하는지 판단하며, 적절한 함수형 인터페이스에 맞춰 실행합니다.
- 특정 객체의 메서드 참조: Stream에서 특정 객체의 메서드 참조를 사용하면, 각 요소에 대해 해당 객체의 메서드를 간편하게 호출할 수 있습니다. 이는 코드의 가독성을 높이고, 중복을 줄이는 데 도움이 됩니다.
'Java' 카테고리의 다른 글
[JAVA] 객체 비교와 Integer 클래스 (0) | 2024.09.09 |
---|---|
[JAVA] #Deque VS Queue #ArrayDeque VS LinkedList #Map (1) | 2024.09.06 |
[JAVA] Java Stream API 활용법 (0) | 2024.09.02 |
[JAVA] 자바 버전별 차이점 (0) | 2024.08.02 |
[JAVA] JAVA로 REST API 구현하기 (0) | 2024.07.19 |