1. Factory method

Factory 메소드는 Interface 내부 또는 private 생성자를 가진 클래스의 companion object에 위치한다.

자바와의 상호 운영을 위해서는 @JvmStatic 어노테이션을 적용 할 것을 권장한다.

interface ImageReader {
    fun read(file: File): Bitmap

    companion object {
        fun newImageReader(format: String) = when (format) {
            "jpg" -> JpegReader()
            "gif" -> GifReader()
            else -> throw IllegalStateException("Unknown format")
        }
    }
}

2. Builder

100% Kotlin 만을 사용하다면 아래에서 보여 주듯 Builder 구현이 불필요하다.

class Burger(
    val patties: Int,
    val hasCheese: Boolean = false,
    val hasTomato: Boolean = false
)

val doubleCheeseBurger = Burger(patties = 2, hasCheese = true)

자바에서 여러 오버로드 메서드를 노출하고 싶다면 @JvmOverloads 어노테이션을 적용한다.

class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) { 
    @JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
        ... 
    }
}

->

// 생성자:
Foo(int x, double y)
Foo(int x)
// 메서드
void f(String a, int b, String c) { } void f(String a, int b) { }
void f(String a) { }

만약 상호 운영이 확실하지 않다면 type-safe builder(aka DSL)를 구현하는 방법이 있다.

data class Burger(val patties: Int, val toppings: List<Toppings>)
enum class Toppings { Cheese, Tomato }

fun burger(patties: Int, init: BurgerBuilder.() -> Unit) =
        BurgerBuilder(patties).apply(init).build()

class BurgerBuilder(private val patties: Int) {
    private var toppings: List<Toppings> = mutableListOf()

    fun toppings(init: List<Toppings>.() -> Unit) {
        toppings.apply(init)
    }

    operator fun Toppings.unaryPlus() {
        toppings += this
    }

    fun build() = Burger(patties, toppings)
}
val doubleCheeseBurger = burger(patties = 2) {
    toppings {
        +Cheese
    }
}

3. Singleton

자바의 패턴을 그대로 복사하여 singleton을 구현할 수도 있지만, kotlin은 object 선언을 지원하여 간단하게 Singleton을 선언 할 수 있게 해준다.

object Singleton {
    //...
}
Singleton.myFunction()

object 클래스의 bytecode를 Java 클래스로 변환하면 다음과 같다.

public final class Singleton {
    public static final Singleton INSTANCE;
    static {
        INSTANCE = new Singleton();
    }
    //...
}

만약 object로 선언된 클래스에 public static가 존재한다면 항상 lazy initialize를 보장한다고 할 수 없지만 (정적 멤버에 접근하려 한다면 클래스가 생성되기 때문이다), kotlin에서는 object 선언 없이는 정적 멤버를 만들 수 없다.

또한 static 블록에서 초기화되므로 Thread-safe하다.

4. 불필요한 인스턴스의 생성을 막자

당연하지만 kotlin에서도 java와 동일한 방식을 사용할 수 있다.

class DontCreateMe private constructor()

하지만 위와 같은 정통적 방식을 사용하는 것이 옳은지를 스스로에게 물어 볼 필요가 있다.

Kotlin의 extension functions으로 관련 객체에 유틸리티 함수를 직접 적용할 수 있다. 즉 TextUtils.isEmpty() 보다는 fun CharSequence.isEmpty()이 더 나은 구현이 될 수 있다. 우연히도 이는 이미 Kotlin standard library에 존재한다. 자바와의 상호운용성을 높이기 위해 @file:JvmName 어노테이션을 파일의 최상단에 추가한다.

@file:JvmName("SnackbarUtils")

import android.support.annotation.ColorInt
import android.support.design.widget.Snackbar

fun Snackbar.setBackgroundColor(@ColorInt color: Int) {
    view.setBackgroundColor(color)
}

5. Dependency injection

Dagger

6. 불필요한 객체의 생성을 피하자

Kotlin의 Int가 프라이머티브 타입 int가 될 지 참조 타입 Integer가 될 지는 컴파일러가 정한다.

Kotlin에서 특별히 조심해야 하는 상황을 보자

Nullable types

Int? 같은 Nullable type은 언제나 Integer로 컴파일 된다. 프라이머티브 타입이 null을 가지게 할 방법이 없기 때문이다.

Kotlin을 개발할 때는 항상 "null이 될 필요가 있나?"를 스스로에게 물어보자.

Objects in collections

콜렉션은 일반적으로 null을 허용하며 제네릭 참조 타입만을 사용 할 수 있다.

안드로이드에서는 이 Auto-boxing을 피하기 위해 SparseIntArray등을 제공한다.

제네릭 클래스의 타입 인자

타입 인자는 언제나 참조 타입이여야 한다.

자세한 내용은 다음을 보기 ->

https://medium.com/@BladeCoder/exploring-kotlins-hidden-costs-part-1-fbb9935d9b62

https://discuss.kotlinlang.org/t/kotlin-and-auto-boxing-of-parameters-and-result/1908/2

9. use extension function

Kotlin은 Java 9의 try-with-resources를 지원하지 않는다. 대신 use extension function을 가지고 있다. 하지만 try-with-resources와 달리 use는 단일 Closeable 리소스에만 적용할 수 있다. 결국 다수의 closeable에는 중첩이 필요하다(try-finally보다는 낫지만...).

inputStream.use {
    outputStream.use {
        // do something with the streams
        outputStream.write(inputStream.read())
    }
}

다수의 리소스드ㄹ을 다루어야 할 일이 많다면 다음 extension function이 도움이 될 것이다.

private inline fun <T : Closeable?> Array<T>.use(block: ()->Unit) {
    var exception: Throwable? = null
    try {
        return block()
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            exception == null -> forEach { it?.close() }
            else -> forEach {
                try {
                    it?.close()
                } catch (closeException: Throwable) {
                    exception.addSuppressed(closeException)
                }
            }
        }
    }
}
arrayOf(inputStream, outputStream).use {
    // do something with the streams
    outputStream.write(inputStream.read())
}

10. equals를 오버라이드 할 때는 보편 계약을 따르자

data class User(val name: String, val age: Int)

성능을 위해서는 다를 가능성이 가장 높고 (즉, 가장 유니크하고) 비교의 비용이 가장 적은 필드가 equals에서 첫 번째로 비교되어야 한다.

그리고 Kotlin 1.2에서는 선언의 순서에 따라 데이터 클래스의 프로퍼티를 비교한다.

data class User(val age: Int, val name: String)

11. equals를 오버라이드 할 때는 hashCode를 오버라이드 하자

data class User(val id: Int, val name: String, val age: Int)

위 데이터 클래스는 다음과 같은 hasCode 함수가 생성된다.

public int hashCode() {
   return (id * 31 + (name != null?name.hashCode():0)) * 31 + age;
}

만약 id가 유일한 값, 즉 uniqueKey라면 다음과 같이 오버라이드하는 것이 성능에 유리할 것이다.

override fun hashCode(): Int {
    return id
}

13. clone은 신중하게 오버라이드하자

kotlin은 data class를 생성하면 Effective Java의 충고에 따라 copy 함수를 생성한다.

14. COMPARABLE 인터페이스의 구현을 고려하자

kotlin에서는 java.util.Comparable대신 kotlin.Comparable을 구현해야 한다. kotlin 버전이 더 많은 확장 함수를 제공한다.

4

5

results matching ""

    No results matching ""