아래 책 공부하며 '새롭게 배운 점', '더 궁금해진 것', '자주 까먹는 부분'들에 대한 포스팅입니다.
코틀린 완벽 가이드 입문부터 활용까지, 필요한 지식 총망라!
페이지 704|ISBN 9791165218911|판형 규격외 변형
"4장 클래스와 객체 다루기"
1. 주 생성자 (Primary constructor)
- 클래스 헤더의 파라미터 목록이며 생략해도 무관
- 실행문 포함되지 않음 => 초기화 처리는 init 블록에서 가능
class Person(name: String) { /* ...클래스 내용... */ } |
ㄴ> constructor 키워드가 생략된 형태
ex) class Person constructor(name: String) { /*생성자 처리 로직*/ } 에서 'constructor'가 생략된 형태
ㄴ> 주 생성자의 접근제한자 지정하려면 생략된 constructor 키워드를 반드시 명시해야 함.
ex) class Person private constructor() { /*클래스 내용*/ }
(생성자 private이면 외부 인스턴스 생성불가형 싱글톤)
- var나 val을 붙이면 클래스의 '프로퍼티'로 정의됨
cf) 위와 같지 않은 생성자 파라미터들은 파라미터 초기화나 init블럭에서만 접근 가능
2. 부 생성자 (Secondary constructors)
- 클래스 내부(=본문=바디)에 constructor 키워드 표시된 블럭 형태
- 명시적으로 '주생성자'가 명시된 경우에는 반드시 this()로 주생성자 위임 호출해야 한다.
class Person { constructor(name: Person) { print("It's a Secondary Constructor") } } |
class Person() { constructor(name: Person) : this() { print("It' a Secondary Constructor") } } |
- ' 주생성자 실행문(=init블록) '은 '부 생성자'보다 항상 먼저 실행된다.
class Person {
init {
println("init block!! (= Execute context of Primary Constructor)")
}
constructor(){
println("Secondary Constructor()")
}
constructor(name: String) : this() {
println("Secondary Constructor(name: String)")
}
constructor(name: String, age: Int) : this(name) {
println("Secondary Constructor(name: String, age: Int)")
}
}
ㄴ> ▼ 위 코드에서 'Person("Jen", 90)'로 객체 생성 시 호출되는 순서
init block!! (= Execute context of Primary Constructor) Secondary Constructor() Secondary Constructor(name: String) Secondary Constructor(name: String, age: Int) |
- 주생성자나 다른 부생성자에게 책임 위임 가능
- 부생성자 파라미터에는 var나 val을 사용할 수 없음
3. 클래스 내부에 존재하는 클래스
- 2종류가 있다.
- 내부 클래스를 포함하는 외부에 존재하는 클래스를 편의상 '외부클래스'라 부르겠다.
3-1. Inner Class
- 외부클래스 인스턴스에 접근 가능
- '외부클래스객체.내부클래스명()' 으로 객체 생성가능. (외부클래스가 먼저 인스턴스화 되어야 호출가능)
- 외부클래스 지칭 시 'this@외부클래스명'으로 가능
3-2. Nested Class
- 외부클래스 인스턴스에 접근 불가능
class Person {
val name: String = "jenna"
inner class InnerPerson {
val name: String = "gara"
fun callName() = name // "gara"
fun callOuterName() = this@Person.name // "jenna"
}
class NestedPerson{
fun test() = name // error : Unresolved reference: name
}
}
+) 지역클래스
- ex) init 블록, 함수 본문 내 정의된 클래스
- 자신을 둘러싼 외부 블럭 내에서만 사용 가능 -> '접근제한자' 사용불가
4. 프로퍼티
- 최상위 프로퍼티 -> 전역변수나 상수와 비슷한 역할
5. 늦은 초기화
5-1. lateinit 키워드
- 변수 초기화 지연
lateinit var 변수명
ㄴ> 조건 : var 변수만 가능 (가변 프로퍼티) / primitive 아닌 non-null 타입만 가능 / 선언과 동시에 값 대입 불가능
ㄴ> getter/setter 지정 불가능
5-2. lazy 프로퍼티
- 프로퍼티를 처음 읽을 때까지 그 값에 대한 계산을 지연함
- 조건 : val (불가변 프로퍼티)
// 프로퍼티값을 읽을때마다 매번 파일 읽어옮
val txtReadLazy by lazy {
// 프로퍼티를 초기화하는 코드
File("d.txt").readText()
}
// cf) ▽ 프로그램 시작 시 바로 파일 읽어옮
val txtReadDirect = File("d.txt").readText()
- '위임 프로퍼티(Delegated property)' 기능의 특별한 케이스
+) '위임 프로퍼티(Delegated property)
- 프로퍼티 처리에 필요한 데이터를 모아 유지하다가 -> 읽기쓰기를 처리하는 '위임객체'를 통해 프로퍼티를 구현하게 해줌
- 스마트캐스트 기능 사용 불가능
- 위임객체
ㄴ> by 키워드 다음에 위치
ㄴ> <코틀린 지원하는 위임객체종류>
1) lazy : 지연 계산을 활성화
2) 프로퍼티 읽기,쓰기마다 리스너를 통지하는 위임
3) 프로퍼티값을 필드대신 맵에 저장하는 위임
등등 더 있다.
6. 클래스 선언
- 싱글톤 객체 선언 : (class키워드 대신) object 키워드로 클래스 선언 (초기화가 싱글턴클래스가 실제 로딩될 시점까지 지연됨, 보통 객체 최초 접근 시점)
7. 내포 객체 (=클래스 안에 존재하는 객체 != 동반 객체)
- 클래스에 내포된 객체도 인스턴스가 생기면 외부클래스의 비공개 멤버에 접근가능함.
- 내포 객체 사용 시, import Application.Factory.create로 팩토리 메서드를 임포트하지 않는 한 매번 내포된 객체의 이름을 지정해야 하므로 동반 객체로 사용해 이 문제를 해결 할 수 있다.
동반 객체(companion object) : companion 키워드를 덧붙인 내포된 객체
ㄴ> - 외부클래스의 멤버에 접근할 때는 동반 객체의 이름을 사용하지 않고 동반 객체가 들어있는 외부 클래스명.동반객체멤버 형태로 사용가능 ex) Application.create(args)
ㄴ> 위와 같이 외부클래스명으로 접근하므로 '동반객체명'은 코드 내 생략 가능하다.
ㄴ> 동반 객체 이름을 생략한 경우 컴파일러는 동반 객체의 디폴트 이름 'Companion'이 사용됨
ex) import Application.Companion.create // OK
ㄴ> 클래스에 동반 객체는 1개만 사용 가능하다.
ㄴ> companion 변경자를 최상위 객체나 다른 객체에 내포된 객체 앞에 붙이는 것은 불가능
ㄴ> '팩토리 디자인 패턴' 구현하는 경우 유용함
ㄴ> ex) 생성자에서 어떤 사전 검사 결과에 따라 널을 반환하거나, (같은 상위 타입에 속하는) 다른 타입의 객체를 반환하고자 하는 경우
생성자는 항상 자신이 정의된 클래스의 객체를 반환하거나 예외를 던질 수만 있어 불가하므로
이런 경우 생성자를 직접 사용하지 않고 생성자를 비공개로 지정해 클래스 외부에서 사용할 수 없게 한 다음,
"내포된 객체"에 팩토리 메서드 역할을 하는 함수를 정의하고, 그 함수 안에서 필요에 따라 객체의 생성자를 호출하는 것이다.
class Application private constructor(val name: String) {
companion object Factory { // companion안쓰면 매번 import 해줘야함
fun create(args: Array<String>): Application? {
val name = args.firstOrNull() ?: return null
return Application(name)
}
}
}
fun main(args: Array<String>) {
val app = Application.create(args) ?: return
println("Application started: ${app.name}")
}
- (전역상태, 외부 클래스의 모든 멤버를 모두 접근가능 등) 자바의 static 문맥과 비슷하지만
'동반객체'의 문맥 : 인스턴스이다. / 더 유연함 / 상위타입을 상속할 수도 있고 일반 객체처럼 전달 가능 /
vs
'자바의 static 문맥' : 정적 멤버 / 덜유연 / 상속X /
- (자바 static 초기화 블록처럼) 동반객체 내부에서 init 블록을 사용가능
8. 객체 식(object expression)
- 명시적인 선언 없이 객체를 바로 생성할 수 있는 특별한 식 (자바 익명 클래스(anonymous class)와 아주 비슷)
fun main() { fun midPoint(xRange: IntRange, yRange: IntRange) = object { val x = (xRange.first + xRange.last)/2 val y = (yRange.first + yRange.last)/2 } val midPoint = midPoint(1..5, 2..6) println("${midPoint.x}, ${midPoint.y}") // (3, 4) } |
- 외부클래스 인스턴스에 접근 가능
fun main() {
var x = 1
val o = object {
fun change() {
x = 2
}
}
o.change()
println(x) // 2
}
- 식이므로 '객체 식'이 만든 값을 변수에 대입할 수 있다.
- 객체 식 안에 정의된 모든 멤버가 들어있는 클래스를 표현하는 익명 객체 타입(anonymous object type)이며,
이런 타입은 단 하나만 존재한다(즉, 멤버가 모두 완전히 똑같은 두 객체 식이 있다고 해도, 둘의 타입은 서로 다르다).
- '익명 객체 타입'은 지역 선언이나 비공개 선언에만 객체식 내부 멤버에 접근할 수 있다.
기타
코틀린은 자바에서 쓰던 유틸리티 클래스(private 생성자로 객체 생성하지 않고 관련 정적메서드를 모아두는 역할)를 만들 필요가 없다.
ㄴ> 이유) 최상위 선언으로 패키지 안에 모아둘 수 있으므로