CH04 코틀린 클래스 객체 인터페이스
아래의 글은 드미트리 제메로프, 스베트라나 이사코바 저/오현석 역, 『Kotlin in Action』,에이콘출판사(2017)의 내용을 기반으로 작성하였습니다.
클래스와 인터페이스
1. 인터페이스에 프로퍼티 선언이 들어갈 수 있다.
2. sealed는 클래스 상속을 제한한다. ( when으로 분기 타는데 상속 받으면 else 에서 처리 될 수도 있음, 이를 막자)
3. 기본 선언은 public final 임
4. 인터페이스에 구현이 있는 메소드 (java 8의 디폴트 메서드와 비슷)
5. 클래스 이름 뒤에 : 인터페이스 혹은 클래스 ( extends와 implements가 별도 없다.)
6. override 변경자 반드시 사용
7. 디폴트 메서드 default 안 붙여도 됨
8. open final abstract 변경자 ( modifier )
9. 상속을 허용 하려면 open을 붙여야 한다.
10. visibility modifier : public , private, protected, internal (자바의 기본인 패키지 전용은 없음), internal : 한꺼번에 컴파일 되는 코틀린 파일
11. 최상위 선언 ( 클래스 함수 프로퍼티 )에 private 가능
12. protected 오직 상속 클래스에서만 접근 ( 자바는 같은 패키지 안에서 접근 가능)
13. 코틀린 nested class : class A, inner class : inter class A ( 자바 nested class : static class A, inner class : class A (클래스안에)
* fragile base class : 하위 클래스가 상속할 경우 상위 클래스의 정확한 규칙을 이해 못하고 구현 할 경우 문제가 발생할 수 있음. effective java에서는 확실하지 않으면 상속 못하게 하던지 설명을 다 넣던지.
interface Clickable {
fun click()
fun showOff() = println("clickable.");
}
interface Focusable {
fun setFocus(value: Boolean) = println("focus { if (value) "on" else "off"}.")
fun showOff() = println("focusable.");
}
class Button : Clickable, Focusable {
override fun click() = println("clicked.")
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
open class RichButton: Clickable {
fun diable() {}
open fun animate() {}
final override fun click() {}
}
abstract class Animated {
abstract fun animate()
}
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr) : Int =
when(e) {
is Expr.Num -> e.value
is Expr.Sum -> e.left + e.right
// else 가 필요 없음 sealed 키워드
// 추후에 Expr에 추가되면 컴파일 오류나고 여기에도 추가해야됨.
}
생성자 관련
14. 기반 클래스 이름 위에는 꼭 빈 괄호가 들어간다.
15. 싱글턴 클래스 > 비공개 생성자 private constructor
16. 부 생성자가 필요한 이유는 자바 상호 운영성
class User(val nickname: String)
class User constructor(_nickname: String) {
val nickname: String
init {
nickname = _nickname
}
}
class User(_nickname: String) {
val nickname = _nickname
}
class User(val nickname: String, val isSubscribed: Boolean = true)
open class Button // 인자가 없는 디폴트 생성자가 만들어진다.
class RadioButton: Button() // Button 클래스의 생성자를 호출
class Secretive private 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) {
}
}
class MyButton2: View {
constructor(ctx: Context) : this(ctx, MYSTYLE) {
}
constructor(ctx: Context, attr : AttributeSet) : super(ctx, attr) {
}
}
인터페이스에 선언된 프로퍼티
interface User {)
val nickname : String
}
class PrivateUser(override val nickname: String) : User
class SubscribeUser(val email: String) : User {
override val nickname: String
// 이 값 불리때 마다 실행
get() = email.substringBefore('@')
}
class FacebookUser(val accountId: Int) : User {
// 방식 초기화시 한번 실행
override val nickname = getFacebookName(accountId)
// getFackbookName은 다른 곳에 정의 된 것으로 가정
}
어떤 값을 저장하되 그 값을 변경하거나 읽을 때 정해진 로직을 실행하는 프로퍼티
field라는 특별한 식별자를 통해서 뒷받침할 필드에 접근 가능
게터에서는 field 값을 읽을 수만 있고, 세터에서는 field 값을 읽고 쓰기 가능
class User(val name: String) {
var address: String = "unknown"
set(value: String) {
println("address changed ${field} -> ${value}")
field = value
}
}
class LengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}