들어가며
이전 글에서는 람다식과 스트림에 대하여 알아봤다.
예시 코드를 들던 중, 아무렇지 않게 작성하고 지나갔던 메소드 참조에 대해서 알아볼까 한다.
메소드 참조(Method References) 란 무엇인가
You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name. - Java doc
람다식을 사용하여 이름없는 메소드를 만든다. 그러나 가끔은 람다식이 기존 메소드를 호출하는 것 외에 아무것도 하지 않을 때가 있다. 이런 경우, 기존 메소드 이름 자체를 참조하는 것이 좀 더 명확한 경우가 있다. '메소드 참조'는 이걸 가능하게 해준다. 이미 이름이 있는 메소드에 대한 간결하고, 읽기 쉬운 람다식이다.
이게 무슨말인고 하니
메소드 참조는 결국 람다식을 한층 더 간결하게 표현해준다는 것이다.
이전 글의 코드로 예를 들어보자
Stream<String> stream1 = Stream.of("a","bb","ccc");
IntStream stream2 = stream1.mapToInt(s -> s.length());
stream2.forEach(System.out::println);
코드를 자세히 보면 이미 메소드참조를 사용하고 있다.
// 람다식
stream2.forEach( str -> System.out.println(str));
// 메소드 참조
stream2.forEach(System.out::println);
// 두 코드의 결과물은 1,2,3으로 같다.
처음에 정의한 것처럼
해당 코드는 기존 메소드를 호출하는 것 외에 아무것도 하지 않기 때문에
인수를 전달하는 것 대신에 메소드 참조를 사용하여
코드를 간결화하였다.
이렇게 보고 나니, 같은 맥락의 코드가 하나가 더 보인다.
// 람다식
IntStream stream2 = stream1.mapToInt(s -> s.length());
// 메소드 참조
IntStream stream2 = stream1.mapToInt(String::length);
이처럼 메소드 참조를 사용하면 기존 람다식을 더욱 간결하게 표현할 수 있다.
메소드 참조 유형(Kinds of Method References)
메소드 참조에는 4가지 유형이 있다.
Kind | Syntax | Examples |
Reference to a static method | ContainingClass::staticMethodName | Person::compareByAge MethodReferencesExamples::appendStrings |
Reference to an instance method of a particular object | containingObject::instanceMethodName | myComparisonProvider::compareByName myApp::appendStrings2 |
Reference to an instance method of an arbitrary object of a particular type | ContainingType::methodName | String::compareToIgnoreCase String::concat |
Reference to a constructor | ClassName::new | HashSet::new |
1. 정적 메소드에 대한 참조 - Reference to a static method
interface Sayable{
void say();
}
public class MethodReference {
public static void saySomething(){
System.out.println("Hello, this is static method.");
}
public static void main(String[] args) {
// 람다식
Sayable sayable1 = () -> MethodReference.saySomething();
// 정적 메소드 참조
Sayable sayable2 = MethodReference::saySomething;
// 인터페이스 메소드 호출
sayable1.say();
sayable2.say();
}
}
// Hello, this is static method.
// Hello, this is static method.
// 같은 출력을 보여준다.
2. Object 인스턴스 메소드에 대한 참조 - Reference to an instance method of a particular object
interface Sayable{
void say();
}
public class InstanceMethodReference {
public void saySomething(){
System.out.println("Hello, this is non-static method.");
}
public static void main(String[] args) {
// 객체 생성
InstanceMethodReference methodReference = new InstanceMethodReference();
// 메소드 참조를 활용한 non static method 참조
Sayable sayable = methodReference::saySomething;
sayable.say();
// 객체를 미리 생성하지 않고 무명 객체로 생성하여 메소드 참조
Sayable sayable2 = new InstanceMethodReference()::saySomething;
sayable2.say();
}
}
1번과 차이라면 static method냐 instance method냐의 차이다.
static method 와 instance method의 간단한 차이는
객체 생성 유무로 호출 가능한지 여부다.
static method는 객체 생성 없이도 '클래스명.메소드 명()' 으로 호출이 가능하지만
(ex, MethodReference.saySomething(); )
instance method는 객체를 생성해 줘야 한다.
(ex, InstanceMethodReference methodReference = new InstanceMethodReference(); )
3. 특정 타입에 대한 인스턴스 메소드에 대한 메소드 참조 - Reference to an instance method of an arbitrary object of a particular type
함수형 인터페이스 Comparator는 동일형 인수 2개를 받아, 순서를 반영한 결과를 int형의 반환값으로 반환하는compare 메소드를 가지고 있다.
메소드 참조를 통해 아래처럼 사용할 수 있다.
Comparator<String> c1 = (x, y) -> x.compareToIgnoreCase(y);
Comparator<String> c2 = String::compareToIgnoreCase;
System.out.println(c1.compare("a","b"));
System.out.println(c2.compare("a","b"));
// -1 로 같은 값 출력
여기서 주의할점은
기본적으로 두번째 인수로부터 첫번째 인수의 순서를 호출하기 때문에
메소드 참조를 사용할 경우,
x.compareToIgnoreCase(y) 는 가능하지만
반대로 y.compareToIgnoreCase(x); 는 불가능하다는 점이다.
compareToIgnoreCase는 대소문자를 무시한다.
위 코드에서 출력값이 -1이 나오는 이유는 첫번째 인수("a")는 두번째 인수("b") 보다
아스키코드 상 -1 만큼의 순서를 가지기 때문이다.
4. 생성자에 대한 메소드 참조 - Reference to a constructor
생성자는 특수한 메소드이기 때문에, 메소드 참조를 사용할 수 있다.
함수형 인터페이스 Function은 하나의 인수를 받아서, 데이터를 변환하고 별도의 형으로 반환 값을 반환하는 apply메소드를 가지고 있다.
Function<String, Intger> f = str -> new Integer(str);
위 코드에서는 String형의 인수를 Integer 클래스의 생성자에 그대로 전달하고 있다.
메소드 참조를 통해 아래처럼 바꿀 수 있다.
Function<String, Intger> f = Integer::new;
마치며
이번에는 메소드 참조에 대해 알아보았다.
물론 무조건적인 축약이 정답은 아닐 수 있다. 오히려 더 헷갈릴 때도 있을 것이다.
이런 부분도 있고 이렇게 표현할 수 있다는 것을 알고 넘어간다면 좋을 것 같다.
이렇게 지식이 또 하나 늘었다.
**참조
https://ko.wikipedia.org/wiki/ASCII
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
'개발자의 삶 > Java' 카테고리의 다른 글
[Java] Comparable vs Comparator (0) | 2023.07.31 |
---|---|
[Java] 람다식(Lambda Expression)과 스트림(Stream)에 대하여 - (2) (1) | 2023.06.01 |
[Java] 람다식(Lambda Expression)과 스트림(Stream)에 대하여 - (1) (0) | 2023.05.31 |
[Java] 오토박싱(Autoboxing), 언박싱(unboxing) (1) | 2023.05.27 |
[Java] 원시 타입(Primitive type), 참조 타입(Reference type), 래퍼클래스(Wrapper Class) (0) | 2023.05.26 |