λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Kotlin

μ΄νŽ™ν‹°λΈŒ μ½”ν‹€λ¦° - 3μž₯ μž¬μ‚¬μš©μ„±

by 주발2 2025. 6. 25.
λ°˜μ‘ν˜•


μ•„μ΄ν…œ 19. knowledge(μ˜λ„μ μΈ 정보)λ₯Ό λ°˜λ³΅ν•˜μ—¬ μ‚¬μš©ν•˜μ§€ 말라

ν•„μžκ°€ μƒκ°ν•˜λŠ” ν”„λ‘œκ·Έλž˜λ°μ˜ κ°€μž₯ 큰 κ·œμΉ™μ€?

  • ν”„λ‘œμ νŠΈμ—μ„œ 이미 있던 μ½”λ“œλ₯Ό λ³΅μ‚¬ν•΄μ„œ λΆ™μ—¬λ„£κ³  μžˆλ‹€λ©΄, 무언가가 잘λͺ»λœ 것이닀.

ν”„λ‘œκ·Έλž¨μ—μ„œ μ€‘μš”ν•œ knowledge 두 κ°€μ§€?

  1. 둜직: ν”„λ‘œκ·Έλž¨μ΄ μ–΄λ–€ μ‹μœΌλ‘œ λ™μž‘ν•˜λŠ”μ§€μ™€ ν”„λ‘œκ·Έλž¨μ΄ μ–΄λ–»κ²Œ λ³΄μ΄λŠ”μ§€
  2. 곡톡 μ•Œκ³ λ¦¬μ¦˜: μ›ν•˜λŠ” λ™μž‘μ„ ν•˜κΈ° μœ„ν•œ μ•Œκ³ λ¦¬μ¦˜

λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ€ μ‹œκ°„μ΄ μ§€λ‚˜λ©΄μ„œ 계속 λ³€ν•˜μ§€λ§Œ, 곡톡 μ•Œκ³ λ¦¬μ¦˜μ€ ν•œ 번 μ •μ˜λœ ν›„ 크게 λ³€ν•˜μ§€ μ•ŠλŠ”λ‹€.

class Student {
    fun isPassing(): Boolean = ...
    fun qualifiesForScholarship(): Boolean = ...
	
    private fun calculatePointsFromPassedCousre(): Int { ... }
}

μœ„ Student ν΄λž˜μŠ€λŠ” μ„œλ‘œ λ‹€λ₯Έ 두 λΆ€μ„œμ—μ„œ μ‚¬μš©μ€‘μΈ 클래슀인데, μš”κ΅¬μ‚¬ν•­μ΄ λ³€κ²½λ˜μ–΄ κΈ°μ‘΄ ν•¨μˆ˜λ“€μ΄ μˆ˜μ •λœλ‹€λ©΄, κ΄€λ ¨ μ—†λŠ” κΈ°λŠ₯μ—μ„œ μ΄μŠˆκ°€ λ°œμƒν•  수 μžˆλ‹€. (νƒ„νƒ„ν•œ λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ μ–΄λŠμ •λ„ μ»€λ²„λŠ” κ°€λŠ₯ν•  것 κ°™λ‹€.)

 

StudentIsPassingValidator, StudentQualifiesForScholarshipValidator λ“±μ˜ 클래슀λ₯Ό κ΅¬λΆ„ν•΄μ„œ ν™œμš©ν•  수 μžˆκ² λ‹€.

λ˜ν•œ μ½”ν‹€λ¦°μ˜ ν™•μž₯ ν•¨μˆ˜λ₯Ό ν™œμš©ν•˜λ©΄, 두 ν•¨μˆ˜λŠ” Student 클래슀 μ•„λž˜μ— λ‘λ©΄μ„œ 각 λΆ€μ„œκ°€ κ΄€λ¦¬ν•˜λŠ” μ„œλ‘œ λ‹€λ₯Έ λͺ¨λ“ˆμ˜ νŒŒμΌμ— λ°°μΉ˜ν•  수 μžˆλ‹€.

// A λͺ¨λ“ˆ
fun Student.qualifiesForScholarship(): Boolean {
    ...
}

// B λͺ¨λ“ˆ
fun Student.isPassing(): Boolean = {
    ...
}

 

헬퍼 ν•¨μˆ˜λŠ” private ν•¨μˆ˜λ‘œ λ§Œλ“€μ§€ μ•Šκ³  λ‹€μŒκ³Ό 같이 λ§Œλ“œλŠ” 것이 μΌλ°˜μ μ΄λ‹€.

  • 두 λΆ€μ„œμ—μ„œ λͺ¨λ‘ μ‚¬μš©ν•˜λŠ” 일반적인 public ν•¨μˆ˜λ‘œ 헬퍼 ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€. λ˜ν•œ 곡톡적인 뢀뢄은 두 λΆ€μ„œμ—μ„œ λͺ¨λ‘ μ‚¬μš©ν•˜λ―€λ‘œ, 이λ₯Ό ν•¨λΆ€λ‘œ μˆ˜μ •ν•  수 없도둝 κ·œμ•½μ„ μ •ν•œλ‹€.
  • 헬퍼 ν•¨μˆ˜λ₯Ό 각각의 λΆ€μ„œ λͺ¨λ“ˆμ— 따라 2개 λ§Œλ“ λ‹€.

 

단일 μ±…μž„ 원칙(SRP)은 μš°λ¦¬μ—κ²Œ 두 κ°€μ§€ 사싀을 μ•Œλ €μ€€λ‹€.

  • μ„œλ‘œ λ‹€λ₯Έ κ³³(λΆ€μ„œ)μ—μ„œ μ‚¬μš©ν•˜λŠ” knowledgeλŠ” λ…λ¦½μ μœΌλ‘œ λ³€κ²½ν•  κ°€λŠ₯성이 λ§Žλ‹€. λ”°λΌμ„œ λΉ„μŠ·ν•œ 처리λ₯Ό ν•˜λ”λΌλ„ μ™„μ „νžˆ λ‹€λ₯Έ knowledge둜 μ·¨κΈ‰ν•˜λŠ” 것이 μ’‹λ‹€.
  • λ‹€λ₯Έ knowledgeλŠ” 뢄리해 λ‘λŠ” 것이 μ’‹λ‹€. κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄ μž¬μ‚¬μš©ν•˜λ €λŠ” 유혹이 λ°œμƒν•  수 μžˆλ‹€.

μ•„μ΄ν…œ 20. 일반적인 μ•Œκ³ λ¦¬μ¦˜μ„ λ°˜λ³΅ν•΄μ„œ κ΅¬ν˜„ν•˜μ§€ 말라

// Bad
override fun saveCallResult(item: SourceResponse) {
	var sourceList = ArrayList<SourceEntity>()
	item.sources.forEach {
		var sourceEntity = SourceEntity()
		sourceEntity.id = it.id
		sourceEntity.category = it.category
		...
	}
	
	db.insertSources(sourceList)
}

// Good
override fun saveCallResult(item: SourceResponse) {
	var sourceEntries = item.sources.map(::sourceToEntry)
	db.insertSources(sourceEntries)
}

private fun sourceToEntry(source: Source) = SourceEntity()
	.apply {
		id = source.id
		category = source.category
		...	
}

 

λͺ¨λ“  ν•¨μˆ˜λŠ” ν…ŒμŠ€νŠΈκ°€ λ˜μ–΄μ•Ό ν•˜κ³ , κΈ°μ–΅λ˜μ–΄μ•Ό ν•˜λ©°, μœ μ§€λ³΄μˆ˜κ°€ λ˜μ–΄μ•Ό ν•œλ‹€. -> λΉ„μš© κ³ λ €

 

ν™•μž₯ ν•¨μˆ˜κ°€ κ°€μ§€λŠ” μž₯점듀?

  • ν•¨μˆ˜λŠ” μƒνƒœλ₯Ό μœ μ§€ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ ν–‰μœ„λ₯Ό λ‚˜νƒ€λ‚΄κΈ° μ’‹λ‹€.
  • ν†±λ ˆλ²¨ ν•¨μˆ˜μ™€ λΉ„κ΅ν•΄μ„œ ν™•μž₯ ν•¨μˆ˜λŠ” ꡬ체적인 νƒ€μž…μ΄ μžˆλŠ” κ°μ²΄μ—λ§Œ μ‚¬μš©μ„ μ œν•œν•  수 μžˆμœΌλ―€λ‘œ μ’‹λ‹€.
  • μˆ˜μ •ν•  객체λ₯Ό μ•„κ·œλ¨ΌνŠΈλ‘œ 전달받아 μ‚¬μš©ν•˜λŠ” 것보닀 ν™•μž₯ λ¦¬μ‹œλ²„λ‘œ μ‚¬μš©ν•˜λŠ” 것이 가독성 μΈ‘λ©΄μ—μ„œ 더 μ’‹λ‹€.
  • IDE의 μžλ™μ™„μ„± 도움을 λ°›μ•„ μ‰½κ²Œ 찾을 수 μžˆλ‹€.
// (1) Stringμ΄λΌλŠ” μƒνƒœλ₯Ό κ°€μ§„ 객체에 isEmail()μ΄λΌλŠ” μˆœμˆ˜ν•œ ν–‰μœ„λ§Œ μΆ”κ°€ν•œ ν˜•νƒœμž…λ‹ˆλ‹€.
fun String.isEmail(): Boolean = this.contains("@") && this.contains(".")
val email = "hello@example.com"
println(email.isEmail())

// (2) List<Int> νƒ€μž…μ—λ§Œ μ μš©λ˜λ―€λ‘œ νƒ€μž…μ΄ λͺ…ν™•ν•œ κ°μ²΄μ—λ§Œ 호좜 κ°€λŠ₯함.
fun List<Int>.sumOfEven(): Int = this.filter { it % 2 == 0 }.sum()

// (3) removeSpaces(raw)보닀 raw.removeSpaces()κ°€ 직관적
fun String.removeSpaces(): String = this.replace(" ", "")
val raw = "hello world"
println(raw.removeSpaces())
λ‚΄κ°€ κ΅¬ν˜„ν•˜λ €λŠ” κΈ°λŠ₯이 이미 μ˜€ν”ˆμ†ŒμŠ€, 라이브러리 등에 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”μ§€ 잘 μ°Ύμ•„λ³΄λŠ” 것도 μ€‘μš”ν•˜λ‹€. 특히 guava, apache λ“±μ˜ μ˜€ν”ˆμ†ŒμŠ€μ—λŠ” ꡉμž₯히 μœ μš©ν•œ λΌμ΄λΈŒλŸ¬λ¦¬λ“€μ΄ ꡉμž₯히 λ§ŽκΈ°μ— ν‰μ†Œμ— 잘 μ±™κ²¨λ³΄λŠ” 것이 μ€‘μš”ν•  것 κ°™λ‹€!

μ•„μ΄ν…œ 21. 일반적인 ν”„λ‘œνΌν‹° νŒ¨ν„΄μ€ ν”„λ‘œνΌν‹° μœ„μž„μœΌλ‘œ λ§Œλ“€μ–΄λΌ

코틀린은 μ½”λ“œ μž¬μ‚¬μš©κ³Ό κ΄€λ ¨ν•˜μ—¬ ν”„λ‘œνΌν‹° μœ„μž„(=ν”„λ‘œνΌν‹° 델리게이트)μ΄λΌλŠ” μƒˆλ‘œμš΄ κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€.

λŒ€ν‘œμ μœΌλ‘œ μ§€μ—° ν”„λ‘œνΌν‹°κ°€ μžˆλŠ”λ°, lazy ν”„λ‘œνΌν‹°λŠ” 처음 μ‚¬μš©ν•˜λŠ” μš”μ²­μ΄ λ“€μ–΄μ˜¬ λ•Œ μ΄ˆκΈ°ν™”λ˜λŠ” ν”„λ‘œνΌν‹°λ₯Ό μ˜λ―Έν•œλ‹€.

val value by lazy { createValue() }

 

μœ„μ™€ 같은 ν”„λ‘œνΌν‹° μœ„μž„μ„ μ‚¬μš©ν•˜λ©΄, λ³€ν™”κ°€ μžˆμ„ λ•Œ 이λ₯Ό κ°μ§€ν•˜λŠ” Obserable νŒ¨ν„΄μ„ μ‰½κ²Œ λ§Œλ“€ 수 μžˆλ‹€.

// λͺ©λ‘μ„ 좜λ ₯ν•˜λŠ” 리슀트 μ–΄λŒ‘ν„°κ°€ μžˆλ‹€λ©΄, λ‚΄λΆ€ 데이터가 변경될 λ•Œλ§ˆλ‹€ λ³€κ²½λœ λ‚΄μš©μ„ λ‹€μ‹œ 좜λ ₯ν•˜κ±°λ‚˜ 둜그둜 λ‚¨κΈ°λŠ” 경우
val items: List<Item> by Delegates.observable(listOf()) { _, _, _ -> notifyDataChanged() }

 

// ### λ·°, λ¦¬μ†ŒμŠ€ 바인딩, μ˜μ‘΄μ„± μ£Όμž…, 데이터 바인딩

// μ•ˆλ“œλ‘œμ΄λ“œμ—μ„œ 뷰와 λ¦¬μ†ŒμŠ€ 바인딩
private val button: Button by bindView(R.id.button)
private val textSize by bindDimension(R.dimen.font_size)

// Kotlinμ—μ„œμ˜ 쒅속성 μ£Όμž…
private val presenter: MainPresenter by inject()
private val repository: NetworkRepository by inject()

// 데이터 바인딩
private val port by bindConfiguration("port")

 

μ½”ν‹€λ¦°μ˜ stdlibμ—μ„œ λ‹€μŒκ³Ό 같은 ν”„λ‘œνΌν‹° λΈλ¦¬κ²Œμ΄ν„°λ₯Ό μ•Œμ•„ λ‘μž.

  • lazy
  • Delegates.obserable
  • Delegates.vetoable
  • Delegates.notNull

 

Delegates λ‚΄λΆ€ μ½”λ“œ

/**
 * Standard property delegates.
 */
public object Delegates {
    /**
     * Returns a property delegate for a read/write property with a non-`null` value that is initialized not during
     * object construction time but at a later time. Trying to read the property before the initial value has been
     * assigned results in an exception.
     *
     * @sample samples.properties.Delegates.notNullDelegate
     */
    public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()

    /**
     * Returns a property delegate for a read/write property that calls a specified callback function when changed.
     * @param initialValue the initial value of the property.
     * @param onChange the callback which is called after the change of the property is made. The value of the property
     *  has already been changed when this callback is invoked.
     *
     *  @sample samples.properties.Delegates.observableDelegate
     */
    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }

    /**
     * Returns a property delegate for a read/write property that calls a specified callback function when changed,
     * allowing the callback to veto the modification.
     * @param initialValue the initial value of the property.
     * @param onChange the callback which is called before a change to the property value is attempted.
     *  The value of the property hasn't been changed yet, when this callback is invoked.
     *  If the callback returns `true` the value of the property is being set to the new value,
     *  and if the callback returns `false` the new value is discarded and the property remains its old value.
     *
     *  @sample samples.properties.Delegates.vetoableDelegate
     *  @sample samples.properties.Delegates.throwVetoableDelegate
     */
    public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
        }

}

 

class User {
    var name: String by Delegates.observable("initial") { prop, old, new ->
        println("Property ${prop.name} changed from $old to $new")
    }
}

fun main() {
    val user = User()
    user.name = "Ju"
    user.name = "Hyun"
}

// Property name changed from initial to Ju
// Property name changed from Ju to Hyun

μ•„μ΄ν…œ 22. 일반적인 μ•Œκ³ λ¦¬μ¦˜μ„ κ΅¬ν˜„ν•  λ•Œ μ œλ„€λ¦­μ„ μ‚¬μš©ν•˜λΌ

νƒ€μž… μ•„κ·œλ¨ΌνŠΈλ₯Ό μ‚¬μš©ν•˜λ©΄ ν•¨μˆ˜μ— νƒ€μž…μ„ 전달할 수 μžˆλŠ”λ°, 이λ₯Ό μ œλ„€λ¦­ ν•¨μˆ˜λΌκ³  λΆ€λ₯Έλ‹€.

  • μ»΄νŒŒμΌλŸ¬κ°€ μ •ν™•ν•œ νƒ€μž… μΆ”μΈ‘

 

μ œλ„€λ¦­ μ œν•œ

  • νƒ€μž… νŒŒλΌλ―Έν„°μ˜ μ€‘μš”ν•œ κΈ°λŠ₯ 쀑 ν•˜λ‚˜λŠ” ꡬ체적인 νƒ€μž…μ˜ μ„œλΈŒνƒ€μž…λ§Œ μ‚¬μš©ν•˜κ²Œ νƒ€μž…μ„ μ œν•œν•˜λŠ” 것

νƒ€μž…(T)에 μ œν•œμ΄ κ±Έλ¦¬λ―€λ‘œ, λ‚΄λΆ€μ—μ„œ ν•΄λ‹Ή νƒ€μž…μ΄ μ œκ³΅ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

 

mapNotNull vs map

mapNotNull

 

map

 

fun main() {
    val items: List<String> = listOf("1", "2", "a", "4", "b")

    val mappedList = items.map { it.toIntOrNull() }
    println("Result of map: $mappedList") // [1, 2, null, 4, null]


    val mappedNotNullList = items.mapNotNull { it.toIntOrNull() }
    println("Result of mapNotNull: $mappedNotNullList") // [1, 2, 4]
}

μ•„μ΄ν…œ 23. νƒ€μž… νŒŒλΌλ―Έν„°μ˜ μ„€λ„μž‰μ„ ν”Όν•˜λΌ

μ„€λ„μž‰μ€ μ§€μ—­ λ³€μˆ˜λ‚˜ νŒŒλΌλ―Έν„°κ°€ λ™μΌν•œ 이름을 κ°€μ§„ μ™ΈλΆ€ μŠ€μ½”ν”„μ˜ λ³€μˆ˜λ₯Ό κ°€λ¦¬λŠ” ν˜„μƒμ΄λ‹€.

class Forest<T> {
    fun addTree(tree: T) {
        // T νƒ€μž…μ˜ λ‚˜λ¬΄λ₯Ό μΆ”κ°€
    }
}

μœ„ μ½”λ“œλŠ” νƒ€μž… μ•ˆμ •μ„±μ΄ λΆ€μ‘±ν•œλ°, μ–΄λ–€ νƒ€μž…μ˜ tree도 λ‹€ 받을 수 있기 λ•Œλ¬Έμ΄λ‹€.

 

class Forest2<T : Tree> {
    fun addTree(tree: T)  {
        // Tree ν•˜μœ„ νƒ€μž…μ˜ T νƒ€μž…μ˜ λ‚˜λ¬΄λ§Œ μΆ”κ°€
    }
}

fun main() {
    val forest = Forest<Birch>()
    forest.addTree(Birch())   // κ°€λŠ₯
    forest.addTree(Spruce())  // 컴파일 였λ₯˜(type mismatch)
}

λ”°λΌμ„œ addTree() ν•¨μˆ˜μ—μ„œ 클래슀 νƒ€μž…μ˜ νŒŒλΌλ―Έν„°μΈ Tλ₯Ό μ‚¬μš©ν•˜λ„λ‘ λ³€κ²½ν•΄μ•Ό ν•œλ‹€.


μ•„μ΄ν…œ 24. μ œλ„€λ¦­ νƒ€μž…κ³Ό variance ν•œμ •μžλ₯Ό ν™œμš©ν•˜λΌ

class Cup<T>

μœ„ μ½”λ“œμ—μ„œ νƒ€μž… νŒŒλΌλ―Έν„° TλŠ” variance ν•œμ •μž(out λ˜λŠ” in)κ°€ μ—†μœΌλ―€λ‘œ, 기본적으둜 invariant(λΆˆκ³΅λ³€μ„±)이닀.

invariantλŠ” μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ λ§Œλ“€μ–΄μ§€λŠ” νƒ€μž…λ“€μ΄ μ„œλ‘œ 관련성이 μ—†λ‹€λŠ” 의미

 

out: 곡변성(covariant)

  • μžλ°” μ œλ„€λ¦­ -> ? extends T
class Cup<out T>
open class Dog
class Puppy : Dog()

fun main() {
    val cupDog: Cup<Dog> = Cup<Puppy>() // OK
    val cupPuppy: Cup<Puppy> = Cup<Dog>() // 였λ₯˜
}

 

"Aκ°€ B의 μ„œλΈŒ νƒ€μž…μΌ λ•Œ, Cup<A>λŠ” Cup<B>의 μ„œλΈŒνƒ€μž…"

 

 

in: λ°˜κ³΅λ³€μ„±(contravariant)

  • μžλ°” μ œλ„€λ¦­ -> ? super T
class Cup<in T>
open class Dog
class Puppy : Dog()

fun main() {
    val cupPuppy: Cup<Puppy> = Cup<Dog>() // OK
    val cupDog: Cup<Dog> = Cup<Puppy>() // 였λ₯˜
}

"Aκ°€ B의 μ„œλΈŒ νƒ€μž…μΌ λ•Œ, Cup<A>λŠ” Cup<B>의 μƒμœ„ νƒ€μž…"

 

variance ν•œμ •μž

 


 

λ°˜μ‘ν˜•

λŒ“κΈ€