Java 8에서 도입된 메소드 레퍼런스(Method Reference)는 Lambda 표현식을 더 간단하게 표현하는 방법입니다.
이 글에서는 메소드 레퍼런스의 기본적인 사용 방법 및 예제에 대해서 알아보겠습니다.
1. 메소드 레퍼런스(Method References)
메소드 레퍼런스는 람다식과 함께 사용될 수 있습니다.
예를들어, 다음은 람다식으로 Hello를 출력하는 코드입니다. (Consumer는 어떤 객체를 입력받아 void를 출력시키는 함수형 인터페이스입니다)
Consumer<String> func = text -> System.out.println(text);
func.accept("Hello");
Output:
Hello
위의 람다식은 다음과 같이 System.out::println
라는 메소드 레퍼런스로 표현할 수 있습니다. String 인자 1개를 받아 void를 출력시키는 함수라는 의미가 생략되어있습니다.
Consumer<String> func = System.out::println;
func.accept("Hello");
Output:
Hello
메소드 레퍼런스는 ClassName::MethodName
형식으로 입력합니다. 메소드를 호출하는 것이지만 괄호()는 써주지 않고 생략합니다.
위의 예제처럼, 메소드 레퍼런스에는 많은 코드가 생략되어있기 때문에 사용하려는 메소드의 인자와 리턴 타입을 알고 있어야 합니다.
메소드 레퍼런스 형태로 작성되는 람다식이 매우 많고, 직접 코딩해보면 모든 코드를 다 써주는 것이 번거롭게 느낄 수 있습니다. 이런 경우 메소드 레퍼런스를 사용하면 매우 편리할 수 있습니다.
메소드 레퍼런스는 사용하는 패턴에 따라 다음과 같이 분류할 수 있습니다.
- Static 메소드 레퍼런스
- Instance 메소드 레퍼런스
- Constructor 메소드 레퍼런스
2. Static 메소드 레퍼런스
Static 메소드 레퍼런스는 Static method를 메소드 레퍼런스로 사용하는 것을 말합니다.
아래 코드에서 변수 exe는 람다식으로 작성한 것이고, 변수 exe2는 메소드 레퍼런스로 작성되었습니다. printSomething 메소드는 인자1개를 받아 void를 리턴하는 static 메소드입니다.
public class Example {
public static void main(String[] args) {
Executable exe = text -> Printer.printSomething(text);
Executable exe2 = Printer::printSomething;
exe.doSomething("do something");
exe2.doSomething("do something");
}
interface Executable {
void doSomething(String text);
}
public static class Printer {
static void printSomething(String text) {
System.out.println(text);
}
}
}
Output:
do somemthing
do somemthing
2.1 Executable을 Consumer로 대체
Custom 인터페이스인 Executable를 Java에서 기본으로 제공하는 함수형 인터페이스인 Consumer로 대체할 수 있습니다. 즉, 굳이 불필요한 인터페이스를 만들지 않고 기본으로 제공하는 인터페이스를 사용할 수 있다는 것입니다.
아래 코드는 Executable을 Consumer로 대체한 코드입니다. 실행 결과는 이전 코드와 동일합니다.
import java.util.function.Consumer;
public class Example {
public static void main(String[] args) {
Consumer<String> exe = text -> Printer.printSomething(text);
Consumer<String> exe2 = Printer::printSomething;
exe.accept("do something");
exe2.accept("do something");
}
public static class Printer {
static void printSomething(String text) {
System.out.println(text);
}
}
}
참고로, Consumer<Type>
는 자바에서 제공하는 함수형 인터페이스입니다. 위의 예제에서 Executable
와 같은 인터페이스를 매번 만들기 어렵기 때문에 자바에서 기본적으로 인터페이스를 제공하고 있습니다. Consumer<String>
은 String 1개를 인자로 받아 void를 리턴하는 메소드를 갖고 있는 인터페이스입니다.
2.2 Stream에서 메소드 레퍼런스 사용
다음 예제는 Stream에서 메소드 레퍼런스를 함께 사용하였습니다. 기존에는 아래 코드에서 1번처럼 람다식으로 길게 작성하던것을 2번과 같이 메소드 레퍼런스를 이용하여 간단하게 표현하였습니다.
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
// 1. lambda expression
companies.stream().forEach(company -> System.out.println(company));
// 2. static method reference
companies.stream().forEach(System.out::println);
}
}
3. Instance 메소드 레퍼런스
Instance 메소드 레퍼런스는 static 메소드가 아닌, 객체의 멤버 메소드를 메소드 레퍼런스로 사용하는 것을 의미합니다.
다음 코드는 Company 객체의 이름을 출력하는 예제입니다. 먼저 메소드 레퍼런스를 사용하지 않고 람다식으로만 구현해보았습니다.
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<Company> companies = Arrays.asList(new Company("google"),
new Company("apple"), new Company("samsung"));
companies.stream().forEach(company -> company.printName());
}
public static class Company {
String name;
public Company(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
}
Output:
google
apple
samsung
위의 코드에서 company -> company.printName()
는 람다식입니다. company객체가 인자로 전달되면 company 객체의 printName()메소드를 호출하라는 뜻인데요.
이것을 메소드 레퍼런스로 표현하면 Company::printName
으로 쓸 수 있습니다. forEach에서 company객체가 전달될 것을 알기 때문에 이렇게 써주면 위처럼 인스턴스의 메소드를 호출해주는 의미로 이해합니다. 실행 결과는 이전 코드와 동일합니다.
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<Company> companies =
Arrays.asList(new Company("google"), new Company("apple"), new Company("samsung"));
companies.stream().forEach(Company::printName);
}
public static class Company {
String name;
public Company(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
}
3.1 Stream에서 메소드 레퍼런스 사용
문자열의 길이를 리턴하는 String.length()
함수를 메소드 레퍼런스로 표현하면 String::length
가 됩니다.
mapToInt()
가 String을 Int로 변환하는 함수입니다. Lamda로 구현하면 mapToInt(company -> company.length())
가 되지만, 간단히 mapToInt(String::length)
로 구현할 수 있습니다.
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
companies.stream()
.mapToInt(String::length)
// .mapToInt(company -> company.length()) /* Lamda expression */
.forEach(System.out::println);
}
}
Output:
6
5
6
5
7
Instance 메소드 레퍼런스는 Static 메소드 레퍼런스와 헷갈릴 수 있는데, 요구하는 인자와 리턴타입을 이미 알고 있다면 어렵지 않게 사용할 수 있습니다.
4. Constructor 메소드 레퍼런스
Constructor 메소드 레퍼런스는 Constructor를 생성해주는 코드입니다.
다음 예제는 람다식으로 생성자를 호출하는 코드입니다.
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
companies.stream()
.map(name -> new Company(name))
.forEach(company -> company.printName());
}
public static class Company {
String name;
public Company(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
}
Output:
google
apple
google
apple
samsung
위 코드를 메소드 레퍼런스를 사용하여 리팩토링하였습니다. Company::new
의 의미는 name -> new Company(name)
와 같습니다.
import java.util.Arrays;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> companies = Arrays.asList("google", "apple", "google", "apple", "samsung");
companies.stream()
.map(Company::new)
.forEach(Company::printName);
}
public static class Company {
String name;
public Company(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
}
위의 코드에서 map은 String 인자가 전달되고, Company로 리턴되길 예상하고 있습니다. Company::new
는 String을 인자로 Company 객체를 생성하여 리턴해줍니다.
5. References
Related Posts
- Java - Unsupported class file major version 61 에러
- Java - String.matches()로 문자열 패턴 확인 및 다양한 예제 소개
- Java - 문자열 공백제거 (trim, replace)
- Java - replace()와 replaceAll()의 차이점
- Java - ArrayList 초기화, 4가지 방법
- Java - 배열 정렬(Sorting) (오름차순, 내림차순)
- Java - 문자열(String)을 비교하는 방법 (==, equals, compare)
- Java - StringBuilder 사용 방법, 예제
- Java - 로그 출력, 파일 저장 방법 (Logger 라이브러리)
- Java IllegalArgumentException 의미, 발생 이유
- Java - NullPointerException 원인, 해결 방법
- Seleninum의 ConnectionFailedException: Unable to establish websocket connection 해결
- Java - compareTo(), 객체 크기 비교
- Java - BufferedWriter로 파일 쓰기
- Java - BufferedReader로 파일 읽기
- Java charAt() 함수 알아보기
- Java - BigInteger 범위, 비교, 연산, 형변환
- Java contains()로 문자(대소문자 X) 포함 확인
- Java - Set(HashSet)를 배열로 변환
- Java - 문자열 첫번째 문자, 마지막 문자 확인
- Java - 문자열 한글자씩 자르기
- Java - 문자열 단어 개수 가져오기
- Java - 1초마다 반복 실행
- Java - 배열을 Set(HashSet)로 변환
- Java - 여러 Set(HashSet) 합치기
- Java - 명령행 인자 입력 받기
- Java - 리스트 역순으로 순회, 3가지 방법
- Java - 특정 조건으로 리스트 필터링, 3가지 방법
- Java - HashMap 모든 요소들의 합계, 평균 계산
- Java - 특정 조건으로 HashMap 필터링
- Java - 싱글톤(Singleton) 패턴 구현
- Java - 숫자 왼쪽에 0으로 채우기
- Java - String 배열 초기화 방법
- Java - 정렬된 순서로 Map(HashMap) 순회
- Java - HashMap에서 key, value 가져오기