Unbound Instance Method Reference

goose_bumps·2024년 11월 29일

궁금증 저장소

목록 보기
1/3

어느 날 메서드 참조를 사용하다가 이런 의문이 들었다.
내가 임의로 만든 클래스의 인스턴스 메서드는 왜 메서드 참조가 안되지?

의문이 든 코드는 이거였다.

public class Main {
    public static void main(String[] args) {
        Function<String,String> f = MyClass::MyMethod;
        Function<String,Integer> f2 = String::length;
    }
}
class MyClass{
    public String MyMethod(String str){
        System.out.println(str);
        return str;
    }
}

static 메서드가 아니기 때문에 불가능하다고 메시지가 출력되었다.

하지만 String클래스의 경우에는 문제가 없었다.

Function<String,Integer> struo = String::length;

하지만 lengh()는 String 클래스의 인스턴스 메서드이다. 왜 이런 문제가 발생할까?

Unbound 인스턴스 메서드 참조의 특성을 이해하면 왜 이런 문제가 발생하는지 알 수 있다.

특정 객체 없이 클래스 이름을 사용하여 인스턴스 메서드를 참조하는 방식으로, 메서드 참조 방법 중 인스턴스 메서드 참조가 바로 Unbound 인스턴스 메서드 참조이다.

객체 바인딩이 없기 때문에 메서드를 참조하는 시점에 호출 객체가 정해지지 않는다. 대신, 나중에 함수형 인터페이스의 메서드를 호출할 때 호출 객체가 전달된다.

        Function<String,Integer> f = String::length; //참조 시점에 호출 객체 정해지지 않음
        f.apply(new String("Hello")); //호출 시 호출 객체가 전달

Unbound 인스턴스 메서드 참조는 다음과 같은 규칙을 따라야 한다.

  • 함수형 인터페이스의 첫 번째 제네릭 타입 매개변수는 호출 객체의 타입으로 사용된다
  • 호출 객체 타입과 함수형 인터페이스의 첫 번째 제네릭 타입은 항상 동일해야 한다
  • 표준 클래스(String,Integer 등)와 사용자 정의 클래스 모두 동일한 규칙을 따른다

자, 이해를 위해 간단한 java.util.function 패키지의 함수형 인터페이스 Function<T,R>을 예로 들어보자.

  • T : 입력 매개변수의 타입
  • R : 반환 타입

여기서 T는 입력 매개변수의 타입인 동시에 호출 객체의 타입으로 사용된다.
위의 코드에서 String의 경우 Function<String,String>매칭이 잘되었기 때문에 가능했던 것이다.
입력 매개변수도 String타입인 동시에 호출 객체 또한 String타입이기 때문이다.

그래서 String클래스의 인스턴스 메서드 중 입력매개변수 타입이 int형인 lastIndexOf(int ch)의 경우에는 컴파일 에러가 발생한다.

        Function<Integer,Integer> f2 = String::lastIndexOf;

왜냐하면 함수형 인터페이스의 첫 번째 타입이 Integer이기 때문에 호출 객체의 타입도 Integer이어야 하는데 여기서는 String이기 때문이다.


자 그렇다면, 사용자 정의 클래스는 어떻게 해야 할까? 입력매개변수 타입과 호출 객체가 다를텐데 방법이 없을까?
BiFunction<T,U,R>을 사용하면 된다.
BiFunction<T,U,R>은 T,U 타입의 매개변수 2개를 받고 R 타입의 반환값을 반환하지만 이 외에도 T에 호출 객체 타입을 받고 U가 메서드 매개변수 타입으로 처리하는데 최적화 되어 있기 때문이다.

class MyClass{
    public String MyMethod(String str){
        System.out.println(str);
        return str;
    }
}

이 사용자 정의 클래스의 메서드의 경우 매개변수는 1개이지만 매개변수 타입과 클래스 타입이 다르기 때문에 BiFunction<T,U,R>과 매칭을 할 경우

  • T : Myclass
  • U : String
  • R : String

으로 처리할 수 있다.

        BiFunction<MyClass,String,String> bf = MyClass::MyMethod;
        bf.apply(new MyClass(), "Hello"); //호출 객체 전달

그래서 apply() 호출 시 원래는 매개변수를 2개 넣지만 이러한 경우에는 첫 번째 매개변수로 호출 객체를 넣어주고, 두 번째 매개변수에는 메서드 매개변수를 입력해주면 된다.

이러면 매개변수가 1개인 메서드여도 BiFunction<T,U,R>을 사용할 수 있다.

하지만 그렇다고 매개변수가 1개인 메서드가 전부 BiFunction<T,U,R>를 사용할 수 있는 건 아니다.

        BiFunction<String,String,Integer> bf = String::length;

위와 같은 경우에는 호출 객체 타입과 입력 매개변수 타입이 일치하기 때문에 BiFunction<T,U,R>가 아닌 Function<String,Integer>을 사용해야 한다.


그렇다면 마지막으로 방금 위에서 설명한

Function<Integer,Integer> f2 = String::lastIndexOf;

이 코드는 어떻게 해야 컴파일 에러가 발생하지 않을까?

우선 람다식으로 풀어보자.

str,i -> str.lastIndexOf(i)

매개변수를 2개를 받아 str은 호출객체로서의 역할을 하는 매개변수, ilastIndexOf()의 입력매개변수로 사용을 하고 제네릭 타입을 <String,Integer,Integer>로 설정하면 된다.
서로 다른 두 입력매개변수 타입을 받아야 하기 때문에 선언타입은 BiFunction으로 사용하면 된다.

BiFunction<String,Integer,Integer> bf = (str, i) -> str.lastIndexOf(i);  
//String::lastIndexOf
BiFunction<String,Integer,Integer> bf = String::lastIndexOf;

이렇게 하면 인스턴스 메서드 참조를 사용할 수 있다.

0개의 댓글