인프런 커뮤니티 질문&답변

RealTake님의 프로필 이미지
RealTake

작성한 질문수

자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)

20강. 코틀린의 scope function

[20강] run 질문

작성

·

173

1

run은 아래와 같이 T에 대한 확장 함수를 받는데

public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}

personReopository의 save를 메서드 레퍼런스로 호출할 때 person 객체 즉 this 를 넘겨야하는데,

위의 run의 구현부를 봤을 때 는 파라미터로 아무 것 도 넘겨 주지 않고 어떻게 this를 받아서 아래 와같이 사용이 될수있는지 궁금합니다.

val person= Person(name: "최태현", age: 100).run(personRepository::save)

답변 2

0

RealTake님의 프로필 이미지
RealTake
질문자

답변 감사 드립니다 ㅠ

진짜 마지막으로궁금한게 있습니다

 

아래 예제로 주신 코드는 실제로 가능한 문법일까요 ?

실제로 해보려는데 안돼서 ,,

val f = Pereson.function() {
  personRepository.save(this)
}

강의에서는 비슷한 문법을 못본것 같아서요ㅜ.

 

아래 처럼은 되는것 같은데ㅜ

위 코드는 단지 이해하기 편하도록 작성해주신건지 궁금합니다

 

 val f:Person.() -> Unit= {
       println(this)
    }

 

최태현님의 프로필 이미지
최태현
지식공유자

아이고 편하게 계속해서 질문 주셔도 괜찮습니다 ㅎㅎㅎ 👍

이해를 돕기 위해 대략 작성한 코드에요!! 컴파일 안되는게 맞을겁니다!

 

대신 이런식으로는 가능합니다!

val f = fun String.(num: Int) { // String이 확장함수의 수신객체 타입이 됩니다!

}

 

작성해주신 방식으로도 가능한데, 작성해주신 방식은 "람다식"을 사용한 방식, 제가 위에 작성한 방식은 fun 키워드를 사용한 방식으로 봐주시면 될 것 같아요!

감사합니다!! 😊 🙏

0

최태현님의 프로필 이미지
최태현
지식공유자

안녕하세요 RealTake님! 🙂 질문 남겨주셔서 감사합니다!

 

질문주신 내용이 두 가지로 해석되어 각각 설명드려보겠습니다.

 

[1. 메소드 레퍼런스 측면]

  • personRepository::Save 라는 메소드 레퍼런스를 사용했을 때

  • 우리가 personRepository.save(person) 처럼 person 이라는 매개변수를 넘기지 않았는데

  • 어떻게 personsave 메소드로 넘어갈 수 있는가!

     

이 기능은 코틀린의 기능이라기 보다는 자바와 코틀린에 동일하게 존재하는 메소드 레퍼런스의 특징입니다!

예를 들어 자바에서도

list.forEach(System.out::println);

이런 코드를 한 번쯤 보셨을 거에요! forEach 를 이용해 list 안에 있는 원소들을 System.out.println(원소) 로 넘겨주는 코드죠!

비슷하게 person 자체가 personRepository.save(person) 의 매개변수로 자연스럽게 들어갔다고 생각할 수 있습니다!

 

[2. run(block: T.() -> R) 측면]

  • run 의 매개변수 block은 T.() -> R 로 매개변수가 존재하지 않습니다! ()

  • 그런데 지금 Person(name: "최태현", age: 100).run(personRepository::save) 이라는 코드는 personRepository.save(person) 처럼 person을 매개변수로 넘기고 있죠!

  • 이게 어떻게 가능한 것인가!

 

이 경우는 Person(name: "최태현", age: 100).run(personRepository::save) 코드를 다음과 같이 단계별로 해석해볼 수 있습니다! 각 단계는 동일한데요, run(personRepository::save) 부분만 떼어서 보도록 하겠습니다.

 

  • run(personRepository::save)

  • run { personRepository.save(this) } <- 이때 this가 pereson 인스턴스가 됩니다.

val f = Pereson.function() {
  personRepository.save(this)
}
Person().run(f)

이 때 잘 보면 우리가 만든 익명 함수 Person.function() 은 매개변수가 없습니다! 단지 매개변수가 없는 그 함수 안에서 personRepository::save 를 호출할 때 this 를 넘겨주고 있는거죠!

아마 마지막 단계로 코드가 분해될 수 있는 것을 이해하시면, 갖고 계신 의문이 쉽게 해결될 수 있을 것 같습니다!! 👍

 

답변이 도움이 되었으면 좋겠습니다. 추가적인 질문 있으시면 언제든 편하게 남겨주세요~!!

감사합니다! 🙏

RealTake님의 프로필 이미지
RealTake
질문자

답변 감사드립니다!

제가 자바만 써와서 그런지
아직 이해가 안되는건 this를 넘겨줄 생각을 자동으로 한게 신기합니다..

자바는 아래 처럼 인터페이스 accept를 사용해서 t라는 이미 넘겨줄 파라미터를 정의 하고있어서 메소드레퍼런스로 넘겨줄 println에 파라미터로 줄 수 있다고 생각이드는데

@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
}

Consumer<String> func = System.out::println;
func.accept("Hello");

 

코틀린은 그런거 없이 줄 수 있는 가장 적절한 파라미터로 똑똑하게 알아서 this를 넘겨준걸까요?

val f = Pereson.function() {
  personRepository.save(this)
}
Person().run(f)
최태현님의 프로필 이미지
최태현
지식공유자

안녕하세요! RealTake님! 🙂

 

먼저 this 자체는 코틀린의 확장함수와 관련되어 있다는 것을 잘 알고 계실 것 같습니다!

예를 들어 아래 함수를 보면, 단순히 function으로 시작하는게 아니라 Person.function 즉, Person 객체의 확장 함수로 동작하는 것을 알 수 있죠!

val f = Pereson.function() {
  personRepository.save(this)
}

확장 함수를 사용하게 되면, 내부적으로 this 를 사용했을 때 확장 함수의 수신 객체 (여기서는 Person 타입의 인스턴스가 됩니다)를 받을 수 있죠!

 

사실 이러한 확장함수는 ByteCode로 만들어 Decompile을 해보면, static 함수 + 첫 번째 매개변수를 Person으로(= 수신객체로) 갖고 있습니다. 예를 들어 위의 코드가 컴파일 되면,

public static void anonymousFunction(Person p) {
  personRepository.save(p);
}

로 변경됩니다.

 

그리고 이제 다음 코드를 생각해보면,

Person().run(personRepository::save)

결국 컴파일 되었을 때 익숙하신 것처럼, "이미 넘겨줄 파라미터를 정의하고 있는 함수를 호출"하는 것과 같다고 생각할 수 있어요! 확장함수를 사용한다는 것은 사실, 수신객체 타입을 매개변수로 받는 static 함수를 사용하는 것과 같기 때문이죠.

 

결론적으로, 겉보기에는 똑똑하게 this를 넘겨준 것처럼 보이고, 실제 컴파일을 하면, 매개변수가 있는 함수 형태로 바뀌기 때문에 기존 자바와 크게 동작이 다르지 않다 라고 이해해주시면 될 것 같습니다.

답변이 도움이 되었으면 좋겠습니다. 감사합니다! 🙏

RealTake님의 프로필 이미지
RealTake

작성한 질문수

질문하기