class Button : Clickable {
override fun click() = println("I was clicked")
}
- 자바에서는 `extends`와 `implements` 키워드를 사용, 코틀린에서는 클래스 이름에 콜론(`:`) 사용
- 자바와 마찬가지로 클래스는 인터페이스는 원하는 만큼 개수 제한없이 구현 가능, 클래스는 하나만 확장 가능
- `override` 변경자를 꼭 사용해야. 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 `overide` 없이 사용 불가
- 인터페이스 메소드도 디폴트 구현을 제공할 수 있다.
- 자바8과 같이 `default`를 붙일 필요 없이, 그냥 메소드 본문을 시그니처 뒤에 추가하면 된다.
```kt
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!") // default 구현
}
interface Focusable {
fun setFocus(b: Boolean) =
println("I ${if (b)} focus.")
fun showOff() = println("I'm clickable!")
}
한 클래스에서 2개의 인터페이스를 구현시, 두 상위 인터페이스에 동일한 메소드가 있는 경우
해당 메소드 구현을 대체할 오버라이딩을 제공하지 않으면 컴파일러 오류가 발생.
아래와 같이 구현해야함.
class Button : Clickable, Focusable {
override fun click() = println("I'm clickable!")
// 이름과 시그니처가 같은 멤버 메소드에 대해서는
// 하위 클래스에서 명시적으로 새로운 구현을 제공해야 함
// (2개 인터페이스의 디폴트 구현은 어느 것도 사용할 수 없다.)
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
코틀린은 자바6과 호환되게 설계
따라서 인터페이스의 디폴트 메소드를 지원하지 않음.
일반 인터페이스와 디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현
4.1.2 open, final, abstract 변경자 : 기본적으로 final
//java
final class A {}
class B { //A class 상속 불가
final test() {// 오버라이딩 불가}
}
자바의 경우, final을 통해 명시적으로 상속을 금지
Fragile base class 라는 하위 클래스가 base 클래스가 변경되어 영향을 받는 문제.
base 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드 할 위험.
"Effective Java" 曰 "상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라"
특별히 하위 클래스에서 오버라이드 하게 의도된 클래스, 메소드가 아니면 final로 만드라.
코틀린은 기본적으로 final
어떤 클래스의 상속을 허용하려면 클래스 앞에 open 변경자를 붙여야 함.
open class RichButton : Clickable { //open 클래스로 상속 가능함.
fun disable() {} // final 함수로 오버라이드 불가능.
open fun animate() {} // 오버라이드 가능
override fun click() {} // *오버라이드한 함수는 기본적으로 open*
final override fun click2() {} // 오버라이드 불가
}
abstract 클래스 선언 가능
추상 클래스는 인스턴스화 불가
추상클래스는 기본이 open
abstract class Animated {
abstract fun animate()
open fun stopAnimating() {} //추상클래스 내에서도 비추상함수는 기본적으로 final
fun animateTwice() //기본 final
}
인터페이스는
final, open, abstract를 사용하지 않는다.
인터페이스 멤버는 항상 열려 있으며 final로 변경할 수 없다.
정리하자면
final : 오버라이드 할 수 없음 (클래스 멤버의 기본 변경자)
open : 오버라이드 할 수 있음 (반드시 open을 명시해야 오버라이드 가능)
abstract : 반드시 오버라이드해야함 (추상 클래스의 멤버에만 사용. 추상 멤버에는 구현이 없어야 한다.)
코틀린은 패키지를 네임스페이스를 관리하기 위한 용도로만 사용. 패키지를 가시성 제어에 사용하지 않음.
대신 internal 사용
의미는 모듈 내부서에만 볼 수 있음.(모듈은 프로젝트 단위, 혹은 컴파일 단위)
접근 제한자(가시성 변경자) 정리
public(기본) : 모든 곳에서 볼 수 있다.
internal : 같은 모듈 안에서만 볼 수 있다.
protected : 하위 클래스 안에서만 볼 수 있다.
private : 같은 클래스 안에서만 볼 수 있다.
internal 같은 모듈?
Modules : The internal visibility modifier means that the member is visible within the same module. More specifically, a module is a set of Kotlin files compiled together, for example:
An IntelliJ IDEA module.
A Maven project.
A Gradle source set (with the exception that the test source set can access the internal declarations of main).
A set of files compiled with one invocation of the Ant task.
4.1.4 내부 클래스와 중첩된 클래스
자바와의 차이는 중첩 클래스(nested class)는 명시적으로 요청없이는 바깥쪽 클래스에 대한 접근권한이 없음.
중첩 클래스(바깥 클래스 참조X) : (java) static class A / (kt) class A
내부 클래스(바깥 클래스 참조O) : (java) class A / (kt) inner class A
interface State: Serializable
interface View {
fun getCurrentState(): State
fun restoreState(state: State) {}
}
class Button : View {
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) {/**/}
class ButtonState : State{/**/} //자바의 static class와 동일
}
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer // 바깥 클래스 Outer의 참조에 접근
}
}
4.1.5 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
sealed class
상위 클래스를 상속한 하위 클래스 정의를 제한할 수 있다.
sealed 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩시켜야 한다.
봉인된 클래스는 클래스 외부에 자신을 상속한 클래스를 둘 수 없다.
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
4.2 뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언
4.2.1 클래스 초기화: 주 생성자
클래스 이름 뒤에 오는 괄호로 둘러싸인 코드가 주 생성자 primary constructor
class User constructor(_nickname: String) { //파라미터가 1개인 주생성자
val nickname: String
init { //초기화 블록
nickname = _nickname //this.nickname=nickname 패턴도 가능
}
}
class User(_nickname: String) {
val nickname = _nickname
}
class User(val nickname: String,
val isSubscribed: Boolean = true) ...
val gil = User("길동")
println(gil.isSubscribed)
true
val gil2 = User("길동2", false)
println(gil2.isSubscribed)
false
val gil3 = User("길동3", isSubscribed=false)
println(gil3.isSubscribed)
false
클래스 외부에서 인스턴스화를 막고 싶을 때
//클래스의 유일한 주 생성자를 비공개처리
class Secretive private constructor() {}
4.2.2 부 생성자
constructor 키워드를 사용
부 생성자를 사용하는 이유 : 자바 상호 운용성, 파라미터 목록이 다른 생성방법이 여럿 존재하는 경우
## 4.2.3 인터페이스에 선언된 프로퍼티 구현
- 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
```kt
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('@') //custom getter
}
class FacebookUser(val accountId: String) : User {
override val nickname: getFacebookName(accontId)
}
인터페이스에는 추상 프로퍼티뿐 아니라 게터/세터가 있는 프로퍼티도 선언 가능
interface User {
val email: String
val nickname: String
get() = email.substringBefore('@')
}
4.2.4 게터와 세터에서 뒷받침하는 필드에 접근
getter와 setter에서는 field 식별자 사용 가능
class User(val name: String) {
var address: String
set(value: String) {
field = vale
}
var counter: Int = 0
private set //외부에서 접근하지 못하도록 setter 접근자의 가시성 변경
}
4.3 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임
4.3.1 모든 클래스가 정의해야 하는 메소드
문자열 표현: toString()
class Client(val name: String, val postalCode: Int) {
override fun toString = "Client(name=$name, postalCode=$postalCode)"
}
객체의 동등성 : equals()
자바와 마찬가지로 동일성 비교를 위해선 euqlas을 사용해야 한다.
== 연산자는 참조 동일성을 검사하지 않고, 객채의 동등성을 검사한다.
val client1 = Client("홍길동")
val client2 = Client("홍길동")
println(client1 == client2) //false
class Client(val name: String, val postalCode: Int) {
override fun equlas(other: Any?): Boolean
if(other == null || other !is Client) //null 및 타입검사
return false
return name == other.name &&
postalCode == other.postalCode
}
- Any는 java.lang.Object에 대응하는 모든 클래스의 최상 클래스
- 해시 컨테이너 : `hashCode()`
- Hash를 사용한 Collections 등에서는 equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.
```kt
class ...
override fun hashCode(): Int = name.hashCode() * 31 + postalCode
4.3.2 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
data 키워드
data class Client(val name: String, val postalCode: Int)
data 키워드를 선언만 해주면 equals, hashcode, tostring 등을 위에서 정의한대로 사용 가능
데이터 클래스와 불변성: copy() 메소드
기본적으로는 var 대신 val 프로퍼티를 통해 immutable 클래스로 사용하기를 권장.
값이 달라져야 하는 경우 copy 메소드를 구현해라
...
fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode)
4.3.3 클래스 위임: by 키워드 사용
상속을 허용하지 않는 클래스에 새로운 동작을 추가해야 할때
데코레이터 패턴
새로운 데코레이터 패턴을 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스를 데코레이터 내부에 필드로 유지하는 것
새로 정의해야 하는 기능은 데코레이터의 메소드에 새로 정의하고, 기존 기능은 기존 메소드의 요청을 그대로 forwarding
class DelegatingCollection<T> : Collection<T> {
private val innerList = arrayListOf<T>()
override val size: Int get() = innerList.size
override fun isEmpty() ...
override fun contains() ...
...
//기본적으로 인터페이스에서 정의된 모든 fun을 override해야함
}
대신
class DelegatingCollection<T> (
innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList<> {// Collection의 구현을 innerList에게 위임한다.
override fun add(...) //내가 필요한 메소드만 새로운 구현이 가능하다.
}
4.4 object 키워드 : 클래스 선언과 인스턴스 생성
4.4.1 객체선언 : 싱글턴을 쉽게 만들기
객체 선언 긴응르 통해 싱글턴을 언어에서 기본 지원한다.
object 사용 시 클래스 선언과 그 클래스의 속한 단일 인스턴스의 선언을 합친 선언
object Payroll {
//싱글턴 인스턴스
}
생성자는 쓸수 없다. 생성자 호출 없이 즉시 만들어 진다.
//예제 : 파일 경로를 대소문자 없이 구분해주는 Comparator를 구현
object CaseInsensitiveFileComparator : Comparator<File> {
override fun compare(file1: File, file2: File): Int {
return file1.path.compareTo(file2.path, ignoreCase = true)
}
}
>>> println(CaseInsensitiveFileComparator.compare(File("/USER",File("/user")))
0
클래스 안에서 객체를 선언할 수도 있다. 역시 인스턴스는 단 하나뿐이다.
바깥 클래스의 인스턴스마다 하나씩 생기는게 아니라
data class Person(..) {
object NameComparator : Comparator<Person> {
override fun compare(...)
}
}
4.4.2 동반 객체 : 팩토리 메소드와 정적 멤버가 들어갈 장소
코틀린은 static 키워드를 지원하지 않는다.
대신 companion object 키워드를 사용해서 동반객체를 사용할 수 있고, static method 호출처럼 사용할 수 있다.
class A {
companion object {
fun bar() {
println("Companion called")
}
}
}
>>> A.bar()
Companion called
동반 객체에서 인터페이스 구현
interface JSONFactory(T) {
fun fromJSON(jsonText: string): T
}
class Person(..) {
companion object: JSONFactory {
override fun fromJson(...) ... //동반 객체가 인터페이스를 구현한다.
}
}
# 4.5 요약
- 코틀린 인터페이스는 디폴트 구현을 포함할 수 있고, 프로퍼티도 포함할 수 있다.
- 기본적으로 `final`, `public` 이다.
- `open`을 사용해서 상속과 오버라이딩을 허용할 수 있다.
- `internal` 선언은 같은 모듈 안에서만 볼 수 있다.
- 중첩 클래스와 `inner`를 사용한 내부클래스를 사용할 수 있다.
- 주 생성자, 부생성자를 활용할 수 있다.
- `data` 키워드를 이용해서 컴파일러가 `equals, hashCode, toString, copy` 등을 자동으로 생성해준다.
- `by` 키워드를 이용하여 위임패턴을 구현할 수 있다.
- `companion` 키워드를 이용해 객체선언을 사용할 수 있다.
이번장은?
class
,interface
object
,by
,companion
등4.1 클래스 계층 정의
4.1.1 코틀린 인터페이스
코틀린 선언은 기본적으로
final
이며public
class Button : Clickable { override fun click() = println("I was clicked") }
한 클래스에서 2개의 인터페이스를 구현시, 두 상위 인터페이스에 동일한 메소드가 있는 경우
아래와 같이 구현해야함.
코틀린은 자바6과 호환되게 설계
4.1.2
open
,final
,abstract
변경자 : 기본적으로final
자바의 경우, final을 통해 명시적으로 상속을 금지
코틀린은 기본적으로
final
어떤 클래스의 상속을 허용하려면 클래스 앞에
open
변경자를 붙여야 함.abstract
클래스 선언 가능open
인터페이스는
final
,open
,abstract
를 사용하지 않는다.final
로 변경할 수 없다.정리하자면
final
: 오버라이드 할 수 없음 (클래스 멤버의 기본 변경자)open
: 오버라이드 할 수 있음 (반드시open
을 명시해야 오버라이드 가능)abstract
: 반드시 오버라이드해야함 (추상 클래스의 멤버에만 사용. 추상 멤버에는 구현이 없어야 한다.)override
: 상위 클래스나 인스턴스의 멤버를 오버라이드 (final
이 명시되면 오버라이드 불가. 기본값은open
)4.1.3 접근 제한자(
visibility modifier
) : 기본적으로public
기본 값은 모두 공개
public
(자바의 경우package-private
)코틀린은 패키지를 네임스페이스를 관리하기 위한 용도로만 사용. 패키지를 가시성 제어에 사용하지 않음.
internal
사용접근 제한자(가시성 변경자) 정리
public
(기본) : 모든 곳에서 볼 수 있다.internal
: 같은 모듈 안에서만 볼 수 있다.protected
: 하위 클래스 안에서만 볼 수 있다.private
: 같은 클래스 안에서만 볼 수 있다.internal 같은 모듈?
Modules : The internal visibility modifier means that the member is visible within the same module. More specifically, a module is a set of Kotlin files compiled together, for example:
4.1.4 내부 클래스와 중첩된 클래스
nested class
)는 명시적으로 요청없이는 바깥쪽 클래스에 대한 접근권한이 없음.static class A
/ (kt)class A
class A
/ (kt)inner class A
4.1.5 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
sealed class
sealed
클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩시켜야 한다.4.2 뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언
4.2.1 클래스 초기화: 주 생성자
class User(_nickname: String) { val nickname = _nickname }
class User(val nickname: String, val isSubscribed: Boolean = true) ...
4.2.2 부 생성자
constructor
키워드를 사용class MyButton : View { constructor(ctx: Context) : super(ctx) { // 상위 클래스의 생성자를 호출 //... } constructor(ctx: Context, attr: AttributeSet) : super(ctx) { //... }
}
4.2.4 게터와 세터에서 뒷받침하는 필드에 접근
getter와 setter에서는
field
식별자 사용 가능4.3 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임
4.3.1 모든 클래스가 정의해야 하는 메소드
문자열 표현:
toString()
객체의 동등성 :
equals()
class Client(val name: String, val postalCode: Int) { override fun equlas(other: Any?): Boolean if(other == null || other !is Client) //null 및 타입검사 return false return name == other.name && postalCode == other.postalCode }
4.3.2 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
data
키워드data
키워드를 선언만 해주면 equals, hashcode, tostring 등을 위에서 정의한대로 사용 가능데이터 클래스와 불변성:
copy()
메소드4.3.3 클래스 위임:
by
키워드 사용대신
4.4
object
키워드 : 클래스 선언과 인스턴스 생성4.4.1 객체선언 : 싱글턴을 쉽게 만들기
4.4.2 동반 객체 : 팩토리 메소드와 정적 멤버가 들어갈 장소
코틀린은
static
키워드를 지원하지 않는다.대신
companion object
키워드를 사용해서 동반객체를 사용할 수 있고, static method 호출처럼 사용할 수 있다.동반 객체에서 인터페이스 구현
class Person(..) { companion object: JSONFactory {
override fun fromJson(...) ... //동반 객체가 인터페이스를 구현한다.
}
}