목록Kotlin/이펙티브 코틀린 (52)
안드로이드 개발자 노트
immutable 컬렉션보다 mutable 컬렉션이 성능적인 측면에서 더 빠릅니다. immutable 컬렉션에 요소를 추가하려면, 새로운 컬렉션을 만들면서 여기에 요소를 추가하기 때문에 새로운 객체 생성 비용이 발생합니다. public operator fun Iterable.plus(elements: Array): List { if (this is Collection) return this.plus(elements) val result = ArrayList() result.addAll(this) result.addAll(elements) return result } immutable 컬렉션은 안전하다는 측면에서 좋지만, 일반적인 지역 변수는 동기화와 캡슐화의 문제에 해당하지 않습니다. 그러므로 지역 변수..
코틀린은 기본 자료형(primitive)을 선언할 수 없지만, 최적화를 위해 내부적으로 사용할 수 있습니다. 기본 자료형은 다음과 같은 특징이 있습니다. 가볍다. 일반적인 객체와 다르게 추가적으로 포함되는 것들이 없기 때문이다. 빠르다. 값에 접근할 때 추가 비용이 들어가지 않는다. 제네릭 타입에는 기본 자료형을 사용할 수 없으므로 랩핑된 타입을 사용해야 하지만, IntArray와 LongArray 등의 기본 자료형을 활용하는 배열을 사용하는 방법이 있습니다. IntArray는 400,000,016바이트, List는 2,000,006,944바이트를 할당하므로 영역만 봤을때, 5배 차이가 발생합니다. 1,000,000개의 숫자를 갖는 컬렉션을 사용해서 평균을 구하는 처리를 해 보면, 배열을 사용하는 경우가..
모든 컬렉션 처리 메서드는 내부적으로 요소들을 활용해 반복을 돌며, 내부적으로 계산을 위해 추가적인 컬렉션을 만들어 사용합니다. 시퀀스 처리도 시퀀스 전체를 랩하는 객체가 만들어지며, 조작을 위해서 또 다른 추가적인 객체를 만들어 냅니다. 요소의 수가 많다면, 큰 비용이 들어갈 수 있으므로, 컬렉션 처리 단계 수를 적절하게 제한하는 것이 좋습니다. 어떤 메서드를 사용하는지에 따라 컬렉션 처리의 단계 수가 달라집니다. class Student(val name: String?) // 작동은 한다. fun List.getNames(): List = this .map { it.name } .filter { it != null } .map { it!! } // 더 좋다. fun List.getNames(): Li..
Iterable과 Sequence는 완전히 다른 형태로 동작합니다. public inline fun Iterable.filter( predicate: (T) -> Boolean ): List { return filterTo(ArrayList(), predicate) } public fun Sequence.filter( predicate: (T) -> Boolean ): Sequence { return FilteringSequence(this, true, predicate) } Sequence 처리 함수들을 사용하면, 데코레이터 패턴으로 꾸며진 새로운 시퀀스가 리턴된다. Sequence는 지연(lazy) 처리되며, 최종적인 계산은 toList또는 count 등의 최종 연산이 이루어질 때 수행된다. Iter..
JVM의 GC가 메모리 관리를 해준다고 메모리 관리를 완전히 무시한다면, 메모리 누수가 발생하여 상황에 따라 OutOfMemoryError(OOME)가 발생할 수 있습니다. 안드로이드를 처음 시작하는 많은 개발자가 흔히 하는 실수로, 객체에 대한 참조를 companion 프로퍼티에 할당해 두는 경우가 있습니다. 이 경우, 가비지 컬렉터가 해당 객체에 대한 메모리 해제를 할 수 없으므로 메모리 누수가 발생합니다. class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //... // this에 대한 레퍼런스 누수 발생 logError = { Lo..
기본 생성자 프로퍼티가 하나인 클래스 앞에 inline을 붙이면, 해당 객체를 사용하는 위치가 모두 해당 프로퍼티로 교체됩니다. inline class Name(private val value: String) { // ... } // 코드 val name: Name = Name("Marcin") // 컴파일 때 다음과 같은 형태로 바뀐다. val name: String = "Marcin" 또한, inline 클래스의 메서드는 모두 정적 메서드로 만들어집니다. inline class Name(private val value: String) { fun greet() { print("Hello, I am $value") } } // 코드 val name: Name = Name("Marcin") name.gre..
일반적인 함수를 호출하면 함수 본문으로 점프하고, 본문의 모든 문장을 호출한 뒤에 함수를 호출했던 위치로 다시 점프하는 과정을 거칩니다. 하지만 '함수를 호출하는 부분'을 '함수의 본문'으로 대체하면, 이러한 점프가 일어나지 않습니다. inline 한정자의 역할은 컴파일 시점에 '함수를 호출하는 부분'을 '함수의 본문'으로 대체하는 것입니다. inline 한정자를 사용하면, 다음과 같은 장점이 있습니다. 타입 아규먼트에 reified 한정자를 붙여서 사용할 수 있다. 함수 타입 파라미터를 가진 함수가 훨씬 빠르게 동작한다. 비지역(non-local) 리턴을 사용할 수 있다. 타입 아규먼트를 reified로 사용할 수 있다 JVM 바이트 코드에는 제네릭이 존재하지 않습니다. 따라서 컴파일을 하면, 제네릭 ..
객체 생성은 언제나 비용이 들어가며, 상황에 따라서는 큰 비용이 들어갈 수도 있습니다. 객체 생성 비용은 항상 클까? 어떤 객체를 랩(wrap)하면, 크게 세 가지 비용이 발생합니다. 객체는 더 많은 용량을 차지한다. 현대 64비트 JDK에서 객체는 8바이트의 배수만큼 공간을 차지하며, 헤더 12바이트까지 최소 16바이트이다. 기본 자료형 int는 4바이트지만, 랩(wrap)되어 있는 Integer는 16바이트이다. 추가로 이에 대한 레퍼런스로 인해 8바이트가 더 필요하여, 5배 이상의 공간을 차지한다고 할 수 있다. 요소가 캡슐화되어 있다면, 접근에 추가적인 함수 호출이 필요하다. 함수를 사용하는 처리는 빠르므로 큰 비용이 발생하지는 않지만, 티끌 모아 태산이 되므로 이 비용도 커질 수 있다. 객체는 ..
확장 함수는 첫 번째 아규먼트로 리시버를 받는 단순한 일반 함수로 컴파일 됩니다. fun String.isPhoneNumber(): Boolean = length == 7 && all { it.isDigit() } // 컴파일 전 fun String.isPhoneNumber(): Boolean = '$this'.length == 7 && '$this'.all { it.isDigit() } // 컴파일 후 확장 함수를 클래스 멤버로 정의할 수도 있고, 인터페이스 내부에 정의할 수도 있습니다. 하지만 이는 사용하지 않는 것이 좋습니다. 첫 번째 이유로는, 가시성을 제한할 수 있다고 생각할 수 있지만 확장만으로 가시성을 제한하지 못합니다. 이는 단순하게 확장 함수를 사용하는 형태를 어렵게 만들 뿐입니다. //..
클래스의 메서드를 정의할 때는 멤버로 정의할 것인지 확장 함수로 정의할 것인지 결정해야 합니다. // 멤버로 메서드 정의하기 class Workshop(/*...*/) { //... fun makeEvent(date: DateTime): Event = //... val permalink get() = "/workshop/$name" } // 확장 함수로 메서드 정의하기 class Workshop(/*...*/) { //... } fun Workshop.makeEvent(date: DateTime): Event = //... val Workshop.permalink get() = "/workshop/$name" 멤버와 확장의 차이점 확장은 따로 가져와서 사용해야 한다. 확장은 우리가 직접 멤버 변수를 추가..