
1. 데이터 클래스 (data class)
안드로이드 개발을 하다 보면 API 응답이나 데이터베이스 모델처럼 데이터를 담아두기 위한 클래스를 많이 만듭니다. 자바에서는 toString(), hashCode(), equals(), copy() 같은 메서드들을 일일이 오버라이드해야 해서 코드가 길고 지저분해졌었습니다. 그런데 코틀린의 data class 하나면 간단히 끝나고 심플해집니다.
// 클래스 앞에 'data' 키워드를 붙여줍니다.
data class Account(val id: String, val password: String)
위의 클래스 처럼 class앞에 data를 붙이면
equals() / hashCode()
객체의 동등성 비교와 해시맵에서 사용할 수 있도록 자동 구현됩니다.
toString()
Account(id=id1004, password=1234) 와 같이 일기 좋은 형태로 자동 생성됩니다.
componentN()
val (id, password) = account 와 같이 객체의 속성을 분해해서 변수에 할당 가능합니다.
copy()
객체를 복사하면서 일부 속성만 변경하고 싶을때 유용합니다.
val account1 = Account("id01", "1234")
val account2 = Account("id01", "1234")
println(account1) // Account(id=id01, password=1234)
println(account1 == account2) // true (내용 같음)
val updatedAccount = account1.copy(id = "id02") // 아이디만 바꿔서 복사
println(updatedAccount) //Account(id=id02, password=1234)
2. 확장함수 (Extensions)
코틀린의 확장 함수를 사용하면 기존 클래스의 코드를 직접 수정하지 않고 새로운 함수를 추가할 수 있습니다.
// String 클래스에 toComma() 함수 추가
fun String.toComma() : String {
val df = DecimalFormat("#,###.########")
val value: Double = (this.toDoubleOrNull()?:let { 0 }).toDouble()
return df.format(value)
}
// Int 클래스에 toDp() 함수 추가
fun Int.toDp(): Int {
val metrics = resources.displayMetrics
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt()
}
// 예시
val value = "1000"
println(value.toComma()) // 1,000
val padding = 32
val paddingInPx = padding.toDp()
println("32dp는 ${paddingInPx}px 입니다.")
라이브러리나 안드로이드 프레임워크의 클래스에도 자유롭게 기능을 추가할ㄹ 수 있어서, 반복되는, 코드를 유틸리티 함수로 만들어 프로젝트 전체에서 편리하게 사용할 수 있습니다.
3. 범위 지정 함수 (Scope Funcions)
코틀린에서는 let, run, with, applay, also라는 5가지 범위 지정 함수가 있습니다. 처음에는 비슷해서 헬갈리지만, 각각의 특징을 이해하면 코드를 간결하고 좋게 만들 수 있습니다. 이 함수들은 특정 객체의 컨텍스트 내에서 코드 블록을 실행하는 공통점이 있습니다.
let
null이 아닌경우만 코드를 실행하고 싶을 때 주로 사용 (null-safe)
account?.let { // account null이 아닐 때만 이 블록이 실행됨
println("아이디: ${it.id}")
}
apply
객체의 속송을 여러개 설정할 때 유용
val dialog = AlertDialog.Builder(this).apply {
setTitle("타이틀")
setMessage("메시지")
setPositiveButton("확인", null)
}.create()
run
apply와 비슷하지만, 마지막 라인의 결과를 리턴합니다. 어떤 객체를 생성하면서 바로 초기화 및 사용이 필요할 때 유용합니다.
val accountState = run {
val account = getAccountFromDb()
if (account.isAdmin) "관리자" else "일반 사용자"
}
4. 봉인된 클래스 (sealed class)
앱의 상태는 다양하게 변합니다. 예를 들어, 네트워크에서 데이터를 불러오는 화면은 성공, 실패, 로딩중 같은 상태를 가질 수 있습니다. 이런 상태들을 관리할때 sealed class를 사용하면 편리합니다.
sealed class는 자기 자신을 상속하는 클래스들의 특정 파일 안으로 제한하는 기능입니다. when 표현식과 함께 사용할 때 컴파일러가 모든 하위 클래스(모든 상태)를 다 처리했는지 체크해 주기 때문입니다. 만약 새로운 상태가 추가되었는데 when문에 빠졌다면, 컴파일 에러를 띄워서 실수를 방지해 줍니다.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Account>) : UiState()
data class Error(val message: String) : UiState()
}
fun handleState(state: UiState) {
when (state) {
is UiState.Loading -> showProgressBar()
is UiState.Success -> showData(state.data)
is UiState.Error -> showErrorDialog(state.message)
// 만약 여기에 새로운 상태를 추가하고 when에서 처리하지 않으면 컴파일 에러!
}
}