Kotlin: Lambda Expression vs Member Reference(::)๐Ÿซข

Murjuneยท2024๋…„ 4์›” 7์ผ
2

kotlin/Java

๋ชฉ๋ก ๋ณด๊ธฐ
4/5
post-thumbnail

์‚ฌ์ง„: Unsplash์˜Mateusz Wacล‚awek

Intro

  • ์•„๋ž˜ ์ง€์‹์„ ์•Œ๋ฉด ์ด ์•„ํ‹ฐํด์„ ์ดํ•ดํ•˜๊ธฐ ๋” ์‰ฝ์Šต๋‹ˆ๋‹ค ๐Ÿ˜ธ
  • ๊ณ ์ฐจํ•จ์ˆ˜: ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋ฐ›๊ฑฐ๋‚˜ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
  • ๋žŒ๋‹ค์‹์€ ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค๋กœ ์ปดํŒŒ์ผ ๋œ๋‹ค.
  • Callable references: ์ƒ์„ฑ์ž, ํ•จ์ˆ˜, ํ”„๋กœํผํ‹ฐ์˜ ์ฐธ์กฐ๊ฐ’(::)์€ ํ•จ์ˆ˜ ํƒ€์ž… ์œผ๋กœ ๋ณ€ํ™˜๋  ์ˆ˜ ์žˆ๋‹ค(๋ชจ๋‘ KCallable์„ ๊ณตํ†ต ์กฐ์ƒ์œผ๋กœ ๊ฐ–๊ณ  ์žˆ๋‹ค)
  • Bounded Member Reference: ํ”„๋กœํผํ‹ฐ๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋‹จ ํ•˜๋‚˜๋งŒ ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜ ๊ฐ’(๋ ˆํผ๋Ÿฐ์Šค)๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์ถ”ํ›„, ํ•ด๋‹น ๋‚ด์šฉ๋“ค์„ ์•Œ๊ธฐ ์‰ฝ๊ฒŒ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค! ๐Ÿ’ช


๋‚˜๋Š” ๊ณ ์ฐจํ•จ์ˆ˜๋ฅผ ๋„˜๊ธธ ๋•Œ ๋ฉค๋ฒ„ ์ฐธ์กฐ์‹ :: ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ์• ์šฉํ•œ๋‹ค.

1) ๋žŒ๋‹ค์‹์— ๋น„ํ•ด ๊ฐ€๋…์„ฑ์ด ์ข‹๋‹ค.
2) ๋žŒ๋‹ค์‹์˜ ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

student.maxBy { it.age }
student.maxBy(Student::age) // ๋” ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋‹ค!

๊ทธ๋Ÿฌ๋‚˜, ๋ ˆ์•„๊ฐ€ ์ถ”์ฒœํ•ด์ฃผ์‹  ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ฝ๊ณ , :: ์„ ์ž˜ ๋ชจ๋ฅด๊ณ  ์‚ฌ์šฉํ–ˆ๋‹ค๋Š” ๊ฒƒ์„ ๊นจ๋‹ฌ์•˜๋‹ค. ๐Ÿฅฒ

๊ทธ๋Ÿผ Quiz๋ฅผ ํ†ตํ•ด ๋…์ž๋“ค์€ ::์™€ ๋žŒ๋‹ค์˜ ์ฐจ์ด๋ฅผ ์ž˜ ์•Œ๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž~

Quiz : ๋„ˆ :: ์™€ ๋žŒ๋‹ค ์ฐจ์ด ์ž˜ ์•Œ๊ณ  ์žˆ๋‹ˆ? ๐Ÿค”

private class ButtonClickListener {
    fun onClick() {
        println("Button clicked")
    }
}

๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ์˜ Action์„ ๋‚˜ํƒ€๋‚ด๋Š” ButtonClickListener ๊ฐ€ ์žˆ๋‹ค.

private class Button(
    private val listener: ClickListener
) {
    fun performClick() = listener.onClick()
}

ํ•ด๋‹น listener๋Š” Button ์˜ ๋ฉค๋ฒ„์ด๊ณ , performClick() ์„ ํ˜ธ์ถœ ์‹œ listener ์˜ ์ด๋ฒคํŠธ๊ฐ€ ์ˆ˜ํ–‰๋œ๋‹ค.

private class ScreenView {
    lateinit var listener: ClickListener
    val button = Button(listener::onClick)}
    val button2 = Button { listener.onClick() 
}

ScreenView ๋Š” listener, button ๊ณผ button2 ๋ฅผ ๋ฉค๋ฒ„๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

  • button์€ ๋žŒ๋‹ค์‹์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค.
  • button2๋Š” ํ”„๋กœํผํ‹ฐ ๋ ˆํผ๋Ÿฐ์Šค๋กœ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค.
fun main() {
    ScreenView().apply {
        listener = ButtonClickListener() // listener ์ดˆ๊ธฐํ™”
        button.performClick()
        button2.performClick()
    }
}

ํ•ด๋‹น main() ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋‚˜์˜ฌ๊นŒ?!
.
.
.
.
.
.
.
.
.
.
.
.
.
.

๋‘๋‘ฅ! ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ์ฃฝ๋Š”๋‹ค! ๐Ÿ’€.
๐Ÿคฏ Error ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‹ˆ listener ๊ฐ€ ์•„์ง ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜๋Š”๋ฐ listener๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํฌ๋ž˜์‰ฌ๊ฐ€ ๋‚ฌ๋‹ค๊ณ  ํ•œ๋‹ค

์šฐ๋ฆฌ๋Š” ๋ถ„๋ช… main() ํ•จ์ˆ˜์—์„œ ScreenView์˜ listener๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์ฃผ์—ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์™œ ์ดˆ๊ธฐํ™” ์—๋Ÿฌ๋กœ ํ”„๋กœ๊ทธ๋žจ์ด ํ„ฐ์งˆ๊นŒ?!
์ด์œ ๋ฅผ ์•Œ๊ธฐ ์œ„ํ•ด์„œ java ์ฝ”๋“œ๋กœ ๋””์ปดํŒŒ์ผ ํ•ด๋ณด์ž ๐Ÿ’ช

์–ด๋””์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๊ฑด์ง€ ๊ณ ๋ฏผํ•ด๋ณธ ํ›„ ์ฝ์–ด๋ณด๋Š”๊ฑธ ์ถ”์ฒœํ•œ๋‹ค.

button ๋””์ปดํŒŒ์ผ

private class ScreenView {
	lateinit var listener: ClickListener
	val button = Button(listener::onClick)
    ...

ํ•ด๋‹น ScreenView ๋ฅผ ๋””์ปดํŒŒ์ผํ•ด๋ณด๋ฉด button์„ ScreenView ์˜ ์ƒ์„ฑ์ž์—์„œ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์žˆ๋‹ค.

public ScreenView() {
    Button var10001 = new Button;
    Function0 var10003 = new Function0() {
        ...

        public final void invoke() {
            ((ButtonClickListener)this.receiver).onClick();
        }
    };
    ButtonClickListener var10005 = this.listener;
    if (var10005 == null) {
        Intrinsics.throwUninitializedPropertyAccessException("listener");
    }

    var10003.<init>(var10005); // listener::onClick ํ• ๋‹น
    var10001.<init>((Function0)var10003); // Button ํ• ๋‹น
    this.button = var10001;
    ...

์—„์ฒญ ๋ณต์žกํ•ด๋ณด์ธ๋‹ค..
์ฐจ๊ทผ์ฐจ๊ทผ ๋œฏ์–ด๋ณด์ž! ๐Ÿ’ช

1) var10003 ๋ถ€๋ถ„

Function0 var10003 = new Function0() {
	...
    
    public final void invoke() {
        ((ButtonClickListener)this.receiver).onClick();
    }
};

Function0 ์ต๋ช… class์˜ ์ธ์Šคํ„ด์Šค๋ฅผ var1003 ๋ณ€์ˆ˜์— ํ• ๋‹นํ•˜๊ณ  ์žˆ๋‹ค.
๊ทธ ์ด์œ ๋Š”, listener::onClick์„ ๋žŒ๋‹ค์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ Button์˜ ์ƒ์„ฑ์ž๋กœ ๋„˜๊ธฐ๊ธฐ ์œ„ํ•จ์ด๋‹ค.

๋žŒ๋‹ค์‹์€ FunctionN ์ธํ…Œํผ์—์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ต๋ช… class์˜ ์ธ์Šคํ„ด์Šค๋‹ค.

Function0 ์ต๋ช… class์˜ invoke() ๋‚ด๋ถ€์— this.receiver ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, receiver ๊ฐ€ ๋ฐ”๋กœ listener::onClick ์˜ ์ˆ˜์‹ ๊ฐ์ฒด listener๋‹ค.
์ฆ‰, invoke() ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ listener.onClick()์ด ์‹คํ–‰๋œ๋‹ค.

lateinit var listener: ClickListener // ์–˜๊ฐ€ this.receiver ๋ฅผ ๋œปํ•จ

์ •๋ฆฌ)

1) listener::onClick ๋ฅผ Function0 ์ต๋ช… class ๋กœ wrappingํ•˜์—ฌ ๋žŒ๋‹ค์‹์œผ๋กœ ๋งŒ๋“ค๊ณ  ์žˆ์Œ

2) button์˜ performClick() ์ด ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์œ„์˜ invoke() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๊ฒƒ์ž„

1) var10005 ๋ถ€๋ถ„ ์ฝ”๋“œ ํ•ด์„

ButtonClickListener var10005 = this.listener;
if (var10005 == null) {
    Intrinsics.throwUninitializedPropertyAccessException("listener");
}

listener๋ฅผ var1005 ๋ณ€์ˆ˜์— ํ• ๋‹นํ•˜๊ณ  ์žˆ๊ณ , var1005 ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์œผ๋ฉด Exception์„ ๋˜์ง„๋‹ค๊ณ  ํ•œ๋‹ค.

๐Ÿค“ ๋ฐ”๋กœ ์ด ๋ถ€๋ถ„์—์„œ ํ”„๋กœ๊ทธ๋žจ์ด ํฌ๋ž˜์‰ฌ๋˜๋Š” ๊ฒƒ์ด๋‹ค!

ScreenView๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋•Œ, listener๊ฐ€ ์•„์ง ์•„์ง ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜๋‹ค.
๋”ฐ๋ผ์„œ, ์œ„ null ๊ฒ€์ฆ๋ฌธ์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•ด UninitializedPropertyAccessException์ด ๋ฐœ์ƒ ๐Ÿ˜ฑ

// ScreenView๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋•Œ null ๊ฒ€์‚ฌํ•จ, ๊ทผ๋ฐ ์•„์ง ์ดˆ๊ธฐํ™” ์•ˆ๋์œผ๋‹ˆ ํŽ‘! ๐Ÿ‘ป
   ScreenView().apply {
        listener = ButtonClickListener() // listener ์ดˆ๊ธฐํ™”
        button.performClick()
        button2.performClick()
    }

๊ทธ๋Ÿผ ์ด์ œ ๋žŒ๋‹ค์‹์„ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•œ button2์˜ ๋””์ปดํŒŒ์ผ๋œ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด๋ณด์ž~

button2 ์˜ ๋””์ปดํŒŒ์ผ ์ฝ”๋“œ

class ScreenView{
	val button2 = Button { listener.onClick() }
	...
    
// java
private static final class ScreenView {
    private final Button button2 = new Button((Function0)(new Function0() {
        public final void invoke() {
           ScreenView.this.getListener().onClick();
        }
    }));
    ...

button์™€ ๋‹ฌ๋ฆฌ button2๋Š” listener์˜ ์ดˆ๊ธฐํ™” ๊ฒ€์‚ฌ๋ฅผ ScreenView๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

@NotNull
public final ButtonClickListener getListener() {
    ButtonClickListener var10000 = this.listener;
    if (var10000 == null) {
        Intrinsics.throwUninitializedPropertyAccessException("listener");
    }

    return var10000;
}

button2 ๋Š” this.getListener() ๋ฅผ ํ˜ธ์ถœ๋  ๋•Œ ์ดˆ๊ธฐํ™” ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ณ  ์žˆ๋‹ค.

buttone2.performClick() // ์ด ๋•Œ listener๊ฐ€ ์ดˆ๊ธฐํ™”๋๋Š”์ง€ ๊ฒ€์‚ฌ

์ฆ‰, buttone2.performClick()์ด ํ˜ธ์ถœ๋  ๋•Œ ๋žŒ๋‹ค์˜ invoke() ํ•จ์ˆ˜์—์„œ ์ดˆ๊ธฐํ™” ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ScreenView๋ฅผ ์ƒ์„ฑํ•  ๋•Œ
๋”ฐ๋ผ์„œ, ๋žŒ๋‹ค๋ฅผ ํ™œ์šฉํ•ด ๊ณ ์ฐจํ•จ์ˆ˜๋ฅผ ๋„˜๊ธธ ๋•Œ๋Š” throwUninitializedPropertyAccessException ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ๐Ÿ˜€

์ •๋ฆฌ

  • bounded member reference: ์ˆ˜์‹  ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์žˆ์–ด์•ผ, ๊ณ ์ฐจํ•จ์ˆ˜๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • lambda expression: invoke()๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ์— ์ˆ˜์‹ ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.

์‚ฌ์‹ค bounded member reference์— ๋Œ€ํ•œ ๊ฐœ๋…์„ ์ž˜ ์•Œ๊ณ  ์žˆ์œผ๋ฉด, ๊ตณ์ด ๋””์ปดํŒŒ์ผํ•˜์ง€ ์•Š์•„๋„ ํ”„๋กœ๊ทธ๋žจ์ด ํ„ฐ์ง„๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์• ์ดˆ์— bounded member reference ์ž์ฒด๊ฐ€ ๋ฉค๋ฒ„ ์ฐธ์กฐ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, ์ธ์Šคํ„ด์Šค๋ฅผ ํ•จ๊ป˜ ์ €์žฅํ•œ ๋‹ค์Œ ๋‚˜์ค‘์— ๊ทธ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด ๋ฉค๋ฒ„๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.
๊ทธ๋Ÿฌ๋‚˜, ์œ„์˜ ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ Listener ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜๋Š”๋ฐ, ํ˜ธ์ถœํ•˜๋‹ˆ ๋‹น์—ฐํžˆ ํ„ฐ์งˆ ์ˆ˜ ๋ฐ–์—...

lateinit var listener: ButtonClickListener
val onClick = ButtonClickListener::onClick // KFunction1<ButtonClickListener, Unit>
val button = Button { onClick(listener) }

val button2 = Button(listener::onClick) // ์œ„์™€ ๋˜‘๊ฐ™๋‹ค

์ถ”๊ฐ€ ํ€ด์ฆˆ

class Button(
    private val onClick: () -> Unit
) {
    fun performClick() = onClick()
}

class ButtonClickListener(
    private val name: String
) {
    fun onClick() {
        print(name)
    }
}

class ScreenView {
    var listener = ButtonClickListener("First")
    val buttonLambda = Button { listener.onClick() }
    val buttonReference = Button(listener::onClick)
}

fun main() {
    val screenView = ScreenView()
    screenView.listener = ButtonClickListener("Second")
    screenView.buttonLambda.performClick()
    screenView.buttonReference.performClick()
}

ํ’€์–ด๋ณด์‹ญ์…”~!!

์ •๋‹ต์€ SecondFirst ์ž…๋‹ˆ๋‹ค. ํฌํฌ

Thanks To Gurgen Gevondov

Reference

https://kt.academy/article/fk-function-references

https://proandroiddev.com/kotlin-lambda-vs-method-reference-fdbd175f6845

profile
์—ด์‹ฌํžˆ ํ•˜๊ฒ ์Šด๋‹ˆ๋‹ค:D

0๊ฐœ์˜ ๋Œ“๊ธ€