우테코 레벨 1 때 UnBound Reference vs Bound Reference
에 대해 학습을 했는데, 글로도 한 번 남기면 좋을 것 같아 레벨 2가 끝나가는 현재 시점에 간략하게나마 정리하고자 한다.
해당 아티클은 람다식과 고차함수의 기본 설명은 건너뛸 것이다.
아래 내용을 학습하고 보시면 좋을 것이에요~😸
해당 아티클을 보고 나서 Kotlin: Lambda Expression vs Member Reference(::)🫢 도 보면 더 많은 것을 알아갈 수 있어요!
- 고차함수: 다른 함수를 인자로 받거나 함수를 반환하는 함수
- 람다식은 함수형 인터페이스를 구현하는 클래스로 컴파일 된다.
- Callable references: 생성자, 함수, 프로퍼티의 참조값(::)은 함수 타입 으로 변환될 수 있다(모두 KCallable을 공통 조상으로 갖고 있다)
Bound Reference: 프로퍼티나 메서드를 단 하나만 호출하는 함수 값(레퍼런스)를 만들어준다.
unBound? Bound? 이게 뭐지 싶을 것이다.
단어가 조금 어렵게 느껴질 수 있는데, 다음과 같은 코드들을 안드로이드 개발이나 코테를 푼 적이 있다면 흔하게 볼 수 있을 것이다.
val recyclerViewAdapter = adater(viewModel::loadSomthing, ..)
1) adapter 의 item 을 클릭했을 때 데이터를 로딩하고 싶을 때, viewModel 의 특정 함수를 고차함수로 넘기고 싶을 때
>> listOf("aaa", "bbb", "c").sortedBy(String::length)
// ["c", "aaa", "bbb"]
2) 코테를 풀 때 특정 조건에서 리스트를 정렬하고 싶을 때
1 번의 경우를 Bound 된 레퍼런스를 사용한 것이고, 2번의 경우가 unBounded 된 레퍼런스를 사용한 것이다!
이제 Bound Reference 와 unBound Reference 의 차이에 대해 예제를 통해 확실히 알아보자!💪
UnBounded Reference
: 특정 class 의 멤버나 함수(생성자 포함)의 참조값을 뜻 한다.UnBounded Reference
를 사용하기 위해 서는 특정 class의 인스턴스가 반드시 필요하다.class Student(val name: String) {
fun study() {
println("$name은 공부중..")
}
}
학생이라는 class 가 있다고 해보자.
// study 저장
val studentStudyFunction: (Student) -> Unit = Student::study
학생이 공부하는 행위(study) 자체를 studentStudyFunction
변수에 저장했다.
어떤 학생이 공부할지 모르기 때문에,
해당 프로퍼티를 호출하기 위해서는 Student(수신객체)가 필요하다.
>> studentStudyFunction(Student("오둥"))
// output :"오둥은 공부중.."
>> studentStudyFunction(Student("꼬상"))
// output :"꼬상은 공부중.."
>> studentStudyFunction(Student("뇽뇽"))
// output :"뇽뇽은 공부중.."
만약, 특정 student 객체 만을 사용하여 호출하고 싶다면 람다식으로 한 번 감싸줘야 한다.
val student = Student("꼬상")
val studyFunction: () -> Unit = { studentStudyFunction(student) }
>> studyFunction() // 꼬상은 공부중..
>> studyFunction() // 꼬상은 공부중..
>> studyFunction() // 꼬상은 공부중..
만약, 특정 객체의 메서드만 계속 사용하고 싶다면 매번 람다식으로 래핑해주는 것은 귀찮다..
그래서, kotlin 1.1 부터는 이러한 람다식 래핑을 자동으로 해주는 bound Reference 기능을 제공한다.
Bound Reference 를 사용하는 방법은 쉽다!
사용하고 싶은 객체::메서드
방식으로 사용하면 된다.
val student = Student("오둥")
val studyFunction: () -> Unit = student::study
>> studyFunction() // 오둥은 공부중..
>> studyFunction() // 오둥은 공부중..
확실히 깔끔한 모습!
이게 어떻게 가능한 것이냐면, student::study
가 할당될 때 student 객체가 내부적으로 필드로 저장된 후, 나중에 stduyFunction()
이 호출될 때 저장한 student 객체를 불러온다.
그래서, studyFunction()
를 호출할 때마다 오둥이만 공부하는 것이다.😎
만약, 특정 객체(e. ViewModel) 의 행동을 람다식의 파라미터나 변수에 할당하고 싶다면 bound Reference 를 사용하자!
val student = Student("오둥")
// unBound reference
val studentStudyFunction = Student::study
val studyFunction = { studentStudyFunction(student) }
// bound reference
val studyFunction2 = student::study
현재 studyFunction 와 studyFunction2 는 완전 동일하다.
실제로 두 함수를 호출해도 동일한 결과값을 갖는다.
>> studyFunction() 오둥은 공부중..
>> studyFunction2() 오둥은 공부중..
그러나, 이건 student 변수가 val(final)
이기 때문에 두 함수가 같은 것이다.
코드를 조금 변형해보겠다.
// val -> var
var student = Student("오둥")
val studentStudyFunction = Student::study
val studyFunction = { studentStudyFunction(student) }
val studyFunction2 = student::study
// 학생을 꼬상으로 바꿨다!
student = Student("꼬상")
> studyFunction()
> studyFunction2()
위 함수를 호출했을 때, 어떻게 될까??
두 함수 모두 꼬상이 공부한다고 나올까?
정답은 studyFunction() 만 꼬상이 공부하는 것으로 바뀐다.
> studyFunction() // 꼬상은 공부중..
> studyFunction2() // 오둥은 공부중..
왜 그럴까??
스스로 한 번 고민해보자!!
.
.
.
.
.
.
.
.
.
.
.
.
.
.
해당 코드를 디컴파일한 코드다
studentFunction (람다식으로 unBound Reference 래핑)
studentFunction
는 호출될 때 student
를 가져와 stduentStudyFunction
의 invoke 함수에 넣어준다.
따라서, 동적으로 student 객체가 바뀌어도 바로 반영이 된다.
studentFunction2 (bound reference)
studentFunction2
는 호출될 때 내부적으로 저장한 student
객체를 (위 사진에서는 receiver) 불러오기 때문에 동적으로 student 객체가 바뀌어도 반영이 되지 않는다.
- Bound Reference: 특정 객체의 프로퍼티나 메서드를 단 하나만 호출하는 함수 값(레퍼런스)를 만들어준다.
- 특정 객체의 멤버를 람다식으로 만들거나 변수에 할당할 것이라면 Bound Reference 를 사용하자!
- 내부적으로 수신 객체를 저장하고 있어, 레퍼런스를 호출할 때 저장된 수신 객체가 메서드를 호출하도록 한다.
- final 이 아닌 프로퍼티(var)의 레퍼런스를 사용할 때는 수신 객체가 동적으로 바꾸지 않음을 인지하자
Kotlin: Lambda Expression vs Member Reference(::)🫢 도 보면 더 많은 것을 알아갈 수 있어요!