목록전체 글 (99)
안드로이드 개발자 노트
모든 컬렉션 처리 메서드는 내부적으로 요소들을 활용해 반복을 돌며, 내부적으로 계산을 위해 추가적인 컬렉션을 만들어 사용합니다. 시퀀스 처리도 시퀀스 전체를 랩하는 객체가 만들어지며, 조작을 위해서 또 다른 추가적인 객체를 만들어 냅니다. 요소의 수가 많다면, 큰 비용이 들어갈 수 있으므로, 컬렉션 처리 단계 수를 적절하게 제한하는 것이 좋습니다. 어떤 메서드를 사용하는지에 따라 컬렉션 처리의 단계 수가 달라집니다. 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" 멤버와 확장의 차이점 확장은 따로 가져와서 사용해야 한다. 확장은 우리가 직접 멤버 변수를 추가..
compareTo 메서드는 수학적인 부등식으로 변환되는 연산자입니다. obj1 > obj2 // obj1.compareTo(obj2) > 0 obj1 = obj2 // obj1.compareTo(obj2) >= 0 obj1 = a 라면, a == b 여야 한다. 즉, 비교와 동등성 비교에 어떠한 관계가 있어야 하며, 서로 일관성이 있어야 한다. 연속적 동작: a >= b 이고 b >= c 라면, a >= c 여야 한다. 마찬가지로 a > b 이고 b > c 라면, a > c 여야 한다. 이러한 동작을 하지 못하면, 요소 정렬이 무한 반복에 빠질 수 있다. 코넥스적 동작: 두 요소는 어떤 확실한 관계를 갖고 있어야 한다. 즉, a >..
해시 테이블은 각 요소에 숫자를 할당하는 함수가 필요합니다. 이 함수를 해시 함수라고 부르며, 같은 요소라면 항상 같은 숫자를 리턴합니다. 해시 함수는 빠르고 충돌이 적을 수록 좋습니다. 가변성과 관련된 문제 요소가 추가될 때만 해시 코드를 계산합니다. 요소가 변경되어도 해시 코드는 계산되지 않으며, 버킷 재배치도 이루어지지 않습니다. 그래서 기본적인 LinkedHashSet와 LinkedHashMap의 키는 한 번 추가한 요소를 변경할 수 없습니다. 세트와 맵의 키로 mutable 요소를 사용하면 안되고, 사용하더라도 요소를 변경해서는 안됩니다. data class FullName( var name: String, var surname: String ) val person = FullName("Maja..