728x90
반응형
✅ 뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언
- 자바에서는 생성자를 하나 이상 선언할 수 있음
- 코틀린도 비슷하지만 바뀐 부분이 있는데 주 생성자와 부 생성자를 구분함
- 주 생성자 : 클래스를 초기화할 대 주로 사용하는 간략한 생성자로, 클래스 본문 밖에서 정의
- 부 생성자 : 클래스 본문 안에서 정의
- 또한 코틀린에서는 초기화 블록을 통해 초기화 로직을 추가할 수 있음
📌 클래스 초기화 : 주 생성자와 초기화 블록
- 보통 클래스의 모든 선언은 중괄호 사이에 들어감
- 클래스 이름 뒤에 오는 괄호로 둘러싸인 코드를 주 생성자라고 부름
- 생성자 파라미터를 지정하고 파라미터에 의해 초기화되는 프로퍼티를 정의하는 두 가지 목적에 쓰임
class User constructor(_nickname : String) { // 파라미터가 하나만 있는 주 생성자
val nickname : String
init { // 초기화 블록
nickname = _nickname
}
}
- 이 예제에서 constructor와 init이라는 새로운 키워드를 볼 수 있으며, 사용 용도는 아래와 같음
- constructor : 주 생성자나 부 생성자 정의를 시작할 때 사용
- init : 초기화 블록을 시작되며 그 안에는 클래스의 객체가 만들어질 때(인스턴스화) 실행될 초기화 코드가 들어감
- 주 생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요
- 필요한 경우 클래스 안에 초기화 블록을 여러개 선언할 수 있음
- 또한 생성자 파라미터 _nickname에서 맨 앞의 밑줄은 프로퍼티와 생성자 파라미터를 구분해줌
- 다른 방법으로 this.nickname = nickname 같은 식을 활용해 모호성을 없애도 됨
- 주 생성자 앞에 별다른 애노테이션이나 가시성 변경자가 없다면 constructor를 생략해도 됨
📌 부 생성자 : 상위 클래스를 다른 방식으로 초기화
- 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다 훨씬 적음
- 자바에서 오버로드한 생성자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름 붙인 인자 문법을 사용해 해결할 수 있음
- 그래도 생성자가 여럿 필요한 경우가 있음
- 일반적으로 프레임워크 클래스를 확장해야 하는데 여러 가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야 하는 경우
open class View {
constructor(ctx : Context) {
// 부 생성자
}
constructor(ctx : Context, attr : AttributeSet) {
// 부 생성자
}
}
- 위의 클래스를 확장할 경우 똑같이 부 생성자를 정의할 수 있음
class MyButton : View {
constructor(ctx : Context) : super(ctx) { // -> 상위 클래스의 생성자 호출
// ...
}
constructor(ctx : Context, attr : AttributeSet) : super(ctx, attr) { // -> 상위 클래스의 생성자 호출
// ...
}
}
- super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출
- 또한 자바와 마찬가지로 생성자에서 this()를 통해 클래스 자신의 다른 생성자를 호출할 수 있음
class MyButton : View {
constructor(ctx : Context) : this(ctx, MY_STYLE) { // -> this를 통해 이 클래스의 다른 생성자에게 위임
// ...
}
constructor(ctx : Context, attr : AttributeSet) : super(ctx, attr) { // -> 여전히 super() 호출
// ...
}
}
📌 인터페이스에 선언된 프로퍼티 구현
- 코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있음
// 추상 프로퍼티 선언이 들어있는 인터페이스 선언의 예
interface User {
val nickname : String
}
- 이는 User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야 한다는 의미
- 인터페이스는 아무 상태도 포함할 수 없기 때문에 상태를 저장할 필요가 있다면 인터페이스를 구현한 하위 클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야 함
// 인터페이스의 프로퍼티 구현하기
class PrivateUser(override val nickname : String) : User // 주 생성자에 있는 프로퍼티
class SubscribingUser(val email : String) : User {
override val nickname : String
get() = email.substringBefore('@') // 커스텀 게터
}
class FacebookUser(val accountId : Int) : User {
override val nickname = getFacebookName(accountId) // 프로퍼티 초기화 식
}
- PrivateUser는 주 생성자 안에 프로퍼티를 직접 선언하는 간결한 구문 사용
- User의 추상 프로퍼티를 구현하고 있기 때문에 override를 표시해야 함
- SubscribingUser는 커스텀 게터로 nickname 프로퍼티를 설정
- FacebookUser에서는 초기화 식으로 nickname 값을 초기화
- 인터페이스에는 추상 프로퍼티뿐 아니라 게터와 세터가 있는 프로퍼티를 선언할 수 있음
- 이때, 게터와 세터는 뒷받침하는 필드를 참조할 수 없음
- 뒷받침하는 필드가 있다면 인터페이스에 상태를 추가하는 셈(인터페이스는 상태를 저장할 수 없음)
- 이때, 게터와 세터는 뒷받침하는 필드를 참조할 수 없음
interface User {
val email: String
val nickName: String
get() = email.substringBefore('@') // 프로퍼티에 뒷받침하는 필드가 없다.대신 매번 결과를 계산해 돌려준다.
}
- 이 인터페이스에는 추상 프로퍼티인 email과 커스텀 게터가 있는 nickname 프로퍼티가 함께 들어있음
- 하위 클래스는 추상 프로퍼티인 email을 반드시 오버라이드해야 함
- 반면 nickname은 오버라이드하지 않고 상속할 수 있음
- 인터페이스에 선언된 프로퍼티와 달리 클래스에 구현된 프로퍼티는 뒷받침하는 필드를 원하는 대로 사용할 수 있음
📌 게터와 세터에서 뒷받침하는 필드에 접근
- 값을 저장하는 동시에 로직을 실행할 수 있게 하기 위해서는 접근자 안에서 프로퍼티를 뒷받침하는 필드에 접근할 수 있어야 함
- 프로퍼티에 저장된 값의 변경 이력을 로그에 남기려는 경우를 생각해보면
- 변경 가능한 프로퍼티를 정의하되 세터에서 프로퍼티 값을 바꿀 때마다 약간의 코드를 추가로 실행해야 함
// 세터에서 뒷받침하는 필드 접근하기
class User(val name : String) {
var address : String = "unspecified"
set(valu : String) {
println("""
Address was changed for $name :
"$field" -> "$value".""".trimIndent()) // 뒷받침하는 필드 값 읽기
field = value // 뒷받침하는 필드 값 변경하기
}
}
val user = User("Alice")
user.address = "Elsenheimerstrasses 47, 80687 Muenchen"
// Address was changed for Alice :
// "unspecified" -> "Elsenheimerstrasse 47, 80687 Muenchen"
- 코틀린에서 프로퍼티의 값을 바꿀 때는 user.adrress = "new value"와 같이 필드 설정 구문을 사용해야함
- 이 구문은 내부적으로 address의 세터를 호출함
- 접근자의 본문에서는 field라는 특별한 식별자를 통해 뒷받침하는 필드에 접근할 수 있음
- 게터에서는 field 값을 읽을 수만 있고, 세터에서는 field 값을 읽거나 쓸 수 있음
📌 접근자의 가시성 변경
- 접근자의 가시성은 기본적으로 프로퍼티의 가시성과 같음
- 하지만 원한다면 get이나 set 앞에 가시성 변경자를 추가해서 접근자의 가시성을 변경할 수 있음
// 비공개 세터가 있는 프로퍼티 선언하기
class LengthCounter {
var counter : Int = 0
private set // 이 클래스 밖에서 이 프로퍼티의 값을 바꿀 수 없음
fun addWord(word : String) {
counter += word.length
}
}
728x90
반응형
'Studying > Kotlin' 카테고리의 다른 글
[Kotlin In Action] 4장. 클래스, 객체, 인터페이스(3) - 컴파일러가 생성한 메서드 : 데이터 클래스와 클래스 위임 (0) | 2023.05.16 |
---|---|
[Kotlin] 코틀린의 Scope Function(범위 지정 함수) (0) | 2023.05.16 |
[Kotlin In Action] 4장. 클래스, 객체, 인터페이스(1) - 클래스 계층 정의 (0) | 2023.05.09 |
[Kotlin In Action] 3장. 함수 정의와 호출(5) - 로컬 함수와 확장 (0) | 2023.05.08 |
[Kotlin In Action] 3장. 함수 정의와 호출(4) - 문자열과 정규식 다루기 (0) | 2023.05.08 |