객체 초기화, getter, setter, 나중 초기화, 지연 초기화, 싱글톤, Nothing 이해하기
3.1 const와 val의 차이
런타임 보다는 컴파일 타임에 변수가 상수임을 나타내야 한다.
- 컴파일 상수(컴파일 시점)에 const 변경자를 사용한다.
- 실행 시간(런타임)에 val 키워드를 사용한다. (java final)
그렇다면 const를 지원하는 이유는 뭘까?
컴파일 상수
- 함수나 클래스의 생성자에게도 결코 할당 될 수 없고 오직 문자열이나 기본 자료형으로 할당 되어야함(클래스의 프로퍼티나 지역변수로 할당 할 수 없으며 문자열 또는 래퍼타입)
- 컴파일 타임 상수는 반드시 객체나 동반 객체 선언의 최상위 속성 또는 멤버여야 한다.(보통 companion objet에 쓰임)
- getter, setter를 가질 수 없다.
const val DEFAULT_PRIORITY = 3
class Task(
val name: String,
_priority: Int = DEFAULT_PRIORITY
) {
companion object {
const val MIN_PRIORITY = 1
}
}
- 코틀린에서 val은 키워드지만 const는 private, inline과 같은 변경자 주의!!
- const는 val과 무조건 같이 쓰임
3.2 사용자 정의 획득자와 설정자 생성하기(getter, setter)
- 기본적으로 public -
- 데이터 은닉을 침해하는 것처럼 보임 - 클래스에서 필드는 직접 선언할 수 없음
// name에는 타입이 무조건 들어가야함
class Task2(val name: String) {
var priority = 3
}
단점
- 인스턴스화 할 때 priority에 값을 할당할 수 없다.
Task2("hello").apply { priority = 3 }
// 범위 지정함수 (apply, run, with, let, also)
- apply블록을 사용해서 priority에 값을 할당 가능
- 쉽게 사용자 정의 획득자와 설정자를 추가할 수 있다
- 생성자에서 선언한 속성에는 할당된 기본값이 있더라도 반드시 타입 정의가 들어가야한다.
https://balldev.tistory.com/84
코틀린 범위 지정 함수(apply, run, with, let, also)
정의 수신 객체 지정 람다함수 특정 객체에 대한 작업을 블록 안에 넣어 실행할 수 있도록 하는 함수 이름에 따라서 범위 지정이 달라진다. apply, run, with, let, also this: 수신 객체를 람다의 수신 객
balldev.tistory.com
class Task2(val name: String) {
var priority = 3
val isLowPriority get() = priority < 3
}
task2.isLowPriority 접근 가능
- 코틀린은 자동으로 지원 필드를 생성한다(get, set)
class Task(
val name: String,
_priority: Int = DEFAULT_PRIORITY
) {
companion object {
const val MIN_PRIORITY = 1
}
// 함수 이름처럼 이름은 마음대로 해도 된다.
var sdf = validPriority(_priority)
set(value) {
field = validPriority(value)
}
private fun validPriority(p: Int) = p.coerceIn(MIN_PRIORITY, 5)
}
- setter가 실행됨
- 함수 이름처럼 원하는 이름으로 할 수 있다.
3.3 데이터 클래스 정의하기
- 데이터를 담는 특정 클래스의 용도를 위해 data 키워드 제공 → 자바 데이터베이스 엔티티와 비슷
- 일관된 equals, hashCode, toString, copy 함수를 생성
data class Product(
val name: String,
val price: Double
)
val p1 = Product("hello", 1.0)
val p2 = Product("hello", 1.0)
println(product1 === product2) // false
println(product1 == product2) // true
// hashCode가 같다.
println("${product1.hashCode()} == ${product2.hashCode()} " )
- 데이터 클래스의 copy 함수 호출하면 얕은 복사가 수행됨
data class Product(var name: String)
data class CopyObject(var product: Product)
val copyObject1 = CopyObject(Product("name"))
val copyObject2 = copyObject1.copy()
println(copyObject1 === copyObject2) // false
println(copyObject1.product === copyObject2.product) // true
- 내부 Product를 공유하고 있음
- 데이터 클래스의 copy는 깊은 복사가 아닌 얕은 복사를 수행한다.
- 하지만 기본형 변수는 깊은 복사가 가능
data class CopyObject2(var name: String)
val copyObject1 = CopyObject2("name")
val copyObject2 = copyObject1.copy()
println(copyObject1 === copyObject2) // false
val p = Product("hello", 1.0)
val (name, price) = p
- 위처럼 구조분해 가능
- 코틀린이 생성하는 함수가 포함된 속성을 사용하고 싶지 않다면 주 생성자가 아닌 클래스 몸체에 속성을 추가하자. - 클래스 안에서 private로 생성하기
https://balldev.tistory.com/85
동등성, 동일성 연산(==, ===)
동등성 동일성 동등성(equals) 오브젝트 서로가 완전히 동일 값이 같은지 동일성(==) 동일한 정보를 가지고 있는 오브젝트 같은 메모리에 다른 오브젝트가 존재하는 경우 주소값이 같은지 자바 ==
balldev.tistory.com
3.4 지원 속성 기법
클래스의 속성을 클라이언트에게 노출하고 싶지만 해당 속성을 초기화하거나 읽는 방법을 제거해야한다.
class Customer(val name: String) {
private var _message: List<String>? = null
val messages: List<String>
get() {
if (_message == null) {
_message = loadMessages()
}
return _message!!
}
private fun loadMessages(): List<String> =
mutableListOf(
"hello",
"sldkf",
"slkdf"
).also { println("loaded messages") }
}
val customer = Customer("name").apply { messages }
val customer2 = Customer("name")
println(customer.messages.size)
3.5 연산자 중복
자신이 작성하지 않은 클래스에도 연산자와 관련된 함수를 추가할 수 있다.
operator fun Point.unaryMinus() = Point(-x, -y)
operator fun Complex.plus(c: Complex) = this.add(c)
operator fun Complex.plus(d: Double) = this.add(d)
...
- equals를 제외한 모든 연산자 함수를 재정의 할 때는 operator 키워드는 필수
- 확장 함수는 자바 클래스의 기존 메서드에 연산을 위임한다.
3.6 나중 초기화 lateinit
생성자 속성 초기화를 위한 정보가 충분하지 않으면 해당 속성을 널 비허용 속성으로 만들고 싶을 경우
- 꼭 필요한 경우에만 사용
- apply를 먼저 고려하자
- lateinit 변경자는 클래스 몸체에서만 선언됨
- 사용자 정의 획득자와 설정자가 없는 var 속성에서만 사용 가능
- 널 할당이 불가능한 타입
- 기본타입에서 사용 불가능
- 변수가 처음 사용되기 전에 초기화 가능
- 객체 바깥쪽에서도 초기화 가능
val lateHello = LateHello()
// 오류남(UninitializedPropertyAccessException)
println(lateHello.name)
class LateHello {
lateinit var name: String
fun initializeName() {
println("${::name.isInitialized}") // false
name = "world"
println("${::name.isInitialized}") // true
}
}
lazy
- 속성에 처음 접근 할 때 수행
- 초기화 비용이 높은데 lazy를 사용하면 초기화는 반드시 실패
- lazy는 val 속성에 사용 가능
선언
class LateHello {
lateinit var name: String
// 처음 호출할 때 한번 호출
val name2: String by lazy {
"skdfjs"
}
fun initializeName() {
println("${::name.isInitialized}")
name = "world"
println("${::name.isInitialized}")
}
}
3.7 equals 재정의를 위해 안전 타입 변환, 래퍼런스 동등, 엘비스 사용
동등 - 자바(equals) 코틀린(== 이 자동으로 equals로 비교)
equals
- 반사성, 대칭성, 추이성, 일관성, null 도 처리 가능해야한다.
- 동등하다고 판단하여 hashCode 도 같아야한다.
코틀린의 equals 구현 (인텔리제이가 생성해준것도 본질적으로 같음)
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherVersion = (other as? KotlinVersion) ?: return false
return this.version == otherVersion.version
}
- === 으로 래퍼런스 동등성 확인
- 인자를 원하는 타입으로 변환하거나 널을 리턴하는 안전 타입 변환 연산자 as?를 사용
num is Int
// as는 형변환이 가능하지 않으면 예외발생
val x: String = y as String
// y가 null이 아니면 String으로 형변환되어 x에 할당
val x: String? = y as? String
- 안전 타입 변환 연산자가 널을 리턴하면 같은 클래스의 인스턴스가 아니므로 동등일 수 없기 때문에 ?: false 반환
- 인스턴스의 version과 other 객체의 version 동등 여부 검사
3.8 싱글톤 생성하기
- 클래스 하나당 인스턴스는 딱 하나만 존재하게 만들고 싶다.
싱글톤 정의 방법
- 클래스의 모든 생성자를 private로 정의
- 해당 클래스를 인스턴스화 하고 그 인스턴스 래퍼런스를 리턴하는 정적 팩토리 메서드를 제공
싱글톤 예시(몇개의 프로세서가 사용 가능한지)
fun main() {
val processors = Runtime.getRuntime().availableProcessors()
}
Runtime 예제
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
// 인스턴스 생성 못하게
private Runtime()
}
// 코틀린에서는 class 대신 object 사용
// 디컴파일 해보면 위의 자바코드와 비슷하게 나옴
object MySingleton {
val myProperty = 3
fun myFunction() = "HELLO"
}
public final class MySingleton {
private static final int myProperty = 3;
public static final MySingleton INSTANCE;
public final void myFunction() {
return "HELLO";
}
}
자바에서 싱글톤 멤버 접근
MySingleton.INSTANCE.myFunction();
MySingleton.INSTANCE.getMyProperty();
코틀린에서 싱글톤 멤버 접근
MySingleton.myFunction()
MySingleton.myProperty
3.9 Noting
절대 리턴하지 않는 함수에 Nothing을 사용한다.
public class Nothing private constructor()
- 밖에서도 안에서도 인스턴스화 할 수 없다. - 인스턴스는 존재하지 않는다.
대표적 예시
fun doNothing() : Nothing = throw Exception("nothing")
주의점: 자바에서는 예외를 던지든 리턴타입이 변경하지 않음
// 구체적인 타입을 명시하지 않는 경우
val x = null
- 이것은 Nothing? 이다.
- Nothing은 모든 타입의 하위타입
'책 > 코틀린 쿡북' 카테고리의 다른 글
4장 - 함수형 프로그래밍 (0) | 2022.09.27 |
---|---|
2장 - 코틀린 기초(문법들 유용하게 사용하기) (0) | 2022.07.09 |
1장 - 코틀린 설치와 실행 (0) | 2022.07.03 |