1. 코틀린 인터페이스는 디폴트 구현을 포함할 수 있으며, 프로퍼티도 포함할 수 있다.
일반 메소드 | 디폴트 메소드 |
무조건 재정의가 필요하다. | 선택적으로 재정의 할 수 있다. |
재정의 하는 경우, override 변경자는 필수이다. |
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!")
}
// implements 하는 경우에는 ":" 로 할 수 있다.
class ClickableImpl : Clickable {
// override 변경자는 필수이다.
override fun click() {
println("I was clicked")
}
// 선택적으로 재정의 할 수 있다.
override fun showOff() {
println("override implemented method")
}
}
fun main() {
val impl = ClickableImpl()
impl.click()
impl.showOff()
}
2. 모든 코틀린 선언은 기본적으로 final 이며, public 이다.
- override 한 메소드는 기본적으로 open 상태이다.
- final override fun xxx() 는 이제 이 함수를 더이상 오버라이드 하지 않겠다는 의미이다.
final | 오버라이드 할 수 없음 |
open | 오버라이드 할 수 있음 |
abstract | 반드시 오버라이드 해야 함 |
override | 오버라이드를 한 것을 의미함 |
3. 가시성 제한자
final | 오버라이드 할 수 없음 |
open | 오버라이드 할 수 있음 |
abstract | 반드시 오버라이드 해야 함 |
override | 오버라이드를 한 것을 의미함 |
4. 코틀린에서 클래스내, 클래스를 생성하는 경우 (nested class)
class Outer {
// 외부에 대한 참조가 있음
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
// 외부에 대한 참조가 없음 (자바의 static class 와 같다.)
class staticInner {
fun getOuterReference() {
}
}
}
5. sealed class
- 상속을 파일내에서 제한하는 sealed 클래스를 사용할 수 있다.
- when 에서 default 분기처리를 하지 않아도 된다.
- sealed 클래스 자체는 private 생성자를 갖는다.
sealed class SealedCls {
class X(val intValue: Int) : SealedCls()
class Y(val stringValue : String) : SealedCls()
}
class Z(val longValue: Long) : SealedCls()
fun printType(type : SealedCls) : String =
when(type) {
is SealedCls.X -> "integer"
is SealedCls.Y -> "string"
is Z -> "long"
}
6. 주 생성자로 객체 초기화하기
- 소괄호"()" 사이에 둘러싸인 코드를 주 생성자(primary constructor)라고 한다.
- 아래 코드의 3가지 선언은 모두 같은 역할을 한다.
- 가시성 선언자가 없다면, constructor 은 생략 가능
class User(val nickname: String)
class User2(_nickname: String) {
val nickname = _nickname
}
class User3 constructor(_nickname: String) {
val nickname: String
init {
nickname = _nickname
}
}
// secondary
class User4 {
val nickname: String
constructor(_nickname: String) {
nickname = _nickname
}
}
7. 외부에서 생성자 사용 못하도록 하기
class PrivateUser private constructor()
8. 부생성자(secondary constructor) 를 이용하여, 상위클래스의 여러가지 생성자를 대응할 수 있다.
- 주로 다른 클래스를 사용하기위해 상속하는경우, 호환성을 위해 사용한다.
open class User {
val nickname: String
val age: Int
constructor(_nickname: String) {
nickname = _nickname
age = 0
}
constructor(_nickname: String, _age: Int) {
nickname = _nickname
age = _age
}
}
class Employee : User {
val pay: Int
// 아래에 정의한 생성자를 this로 이용하였다.
constructor(_nickname: String, _pay: Int) : this(_nickname, 0, _pay)
constructor(_nickname: String, _age: Int, _pay: Int) : super(_nickname, _age) {
pay = _pay
}
}
9. 인터페이스 프로퍼티
- 코틀린에서는 인터페이스에
- 프로퍼티 (멤버변수 + Getter + Setter) 선언이 가능하다.
- 멤버변수만 선언했다면, 구현하는 클래스에서 이에 맞는 getter / setter 를 구현해줘야한다.
- val : getter
- var : setter + getter
interface User {
val nickname: String
}
// 주생성자를 통하여 getter 를 구현
class PrivateUser(override val nickname: String) : User
// 커스텀게터를 구현
class SubscribingUser(val email: String) : User {
override val nickname: String
get() = email.substringBefore('@')
}
10. field(뒷바침하는 필드) 에 접근
- 커스텀 getter / setter 에서, field 는 실제 값을 저장하는 변수를 뜻한다.
- 커스텀 setter 에서, value 를 이용하여 field 값을 set 할 수 있다.
class User(name: String, age: Int) {
val name: String = name
get() {
return field
}
var age: Int = age
get() {
return field
}
set(value) {
field = value
}
}
11. 접근자 가시성 변경
- private set 등을 이용해서 접근자 가시성을 변경할 수 있다.
- 아래의 예시에서는 name 을 세팅하는 경우,
- firstName, secondName 은 해당 클래스 내에서만 변경할 수 있도록 해놓았다.
class User {
var name: String = ""
set(value) {
field = value
firstName = value.substringBefore(" ")
secondName = value.substringAfter(" ")
}
var firstName : String = ""
private set
var secondName : String = ""
private set
fun getFullName(): String = "firstname is $firstName and secondname is $secondName"
}
12. toString / equals / hashCode
- equals
- 코틀린에서는 '==' 을 이용하여 equals 비교를 한다.
- '===' 을 이용하여 reference 를 비교할 수 있다.
- hashCode
- hash 컨테이너를 사용할 때, 키로 사용할 수 있다.
13. 데이터클래스 (toString + equals + hashCode + copy + ...)
- 데이터클래스를 사용하므로써, 위에 명시한 편의메소드를 코틀린 컴파일러가 생성해준다.
- copy 메소드는 Shallow copy 를 지원한다.
data class DataCls(val name: String, val age: Int, val innerCls : Inner)
class Inner
fun main() {
val inner = Inner()
val a = DataCls("lee", 20, inner)
val b = DataCls("lee", 20, inner)
assertTrue(a==b)
assertFalse(a===b)
val of = setOf<DataCls>(a)
assertTrue(of.contains(a))
val copy = a.copy()
assertTrue(a==b)
assertFalse(a===copy)
assertTrue(a.innerCls===copy.innerCls)
}
14. 클래스 위임을 통한 불필요 코드 implements 하지 않기
- 자바로 변환하여 보면,
- 아래의 추가로 오버라이드 한, add / addAll 을 제외한 함수들은 by 키워드를 통해 innerSet에 위임하였다.
class CountingSet<T>(
val innerSet: MutableCollection<T> = HashSet<T>()
) : MutableCollection<T> by innerSet {
var addedCnt = 0
private set
override fun add(element: T): Boolean {
addedCnt++
return innerSet.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
addedCnt+= elements.size
return innerSet.addAll(elements)
}
}
15. object 키워드를 통한 싱글턴 생성
- object 키워드 : 클래스를 정의하면서, 인스턴스를 생성한다.
- 생성자를 가질 수 없다.
- 상속이나 구현은 가능하다.
- nested object 또한, 인스턴스는 한개만 생성된다.
object Calculator {
fun plus(a: Int, b: Int): Int = a + b
fun minus(a: Int, b: Int): Int = a - b
}
fun main() {
assertSame(3, Calculator.plus(1, 2))
assertSame(-1, Calculator.minus(1, 2))
}
자바로 디컴파일한 결과이다.
- private 생성자를 가진 INSTANCE 를 제공한다.
public final class Calculator {
@NotNull
public static final Calculator INSTANCE = new Calculator();
private Calculator() {
}
public final int plus(int a, int b) {
return a + b;
}
public final int minus(int a, int b) {
return a - b;
}
}
16. 동반 객체 (companion object)
- 클래스 내부에 정의하여, 클래스와 하나로 사용할 수 있다.
- 코틀린은 static 키워드를 지원하지 않는다.
- 최상위함수 / 객체 선언 을 통해 이를 대신할 수 있다.
- 객체선언은 클래스의 private 멤버에 접근할 수 있다. (아래의 팩토리 패턴을 사용할 때 유용하다.)
class A {
companion object Inside {
fun bar() {
println("Inside Companion Object")
}
}
}
fun main() {
A.bar()
A.Inside.bar()
}
17. 동반 객체를 이용한 팩토리 패턴
- 동반 객체를 사용하는 경우, 클래스 내부의 private 멤버에 접근할 수 있다.
- 이를 이용하여 팩토리 패턴을 사용할 수 있다.
class Account private constructor(val nickname: String) {
companion object {
fun createNaverAccount(nickname : String) = Account("$nickname@naver.com")
fun createKakaoAccount(nickname : String) = Account("$nickname@kakao.com")
}
}
fun main() {
val naver = Account.createNaverAccount("lsj")
val kakao = Account.createKakaoAccount("lsj")
assertEquals("lsj@naver.com", naver.nickname)
assertEquals("lsj@kakao.com", kakao.nickname)
}
18. 동반 객체 확장
- 동반 객체를 확장하기 위해서는, 비어있는 companion 오브젝트가 필수이다.
- 동반 객체의 기본이름인 Companion 으로 접근할 수 있다.
data class Person(val name: String) {
companion object {
}
}
fun Person.Companion.fromJson(json: String) : Person {
return Person(json)
}
fun main() {
val fromJSON = Person.fromJson("test")
assertEquals("test", fromJSON.name)
}
19. object 키워드를 이용한 Anonymous 클래스 작성
- object 키워드를 통해 아래의 예시처럼 구현할 수 있다.
- 인터페이스 구현
- 클래스 확장
- object 키워드를 사용했더라도, 싱글턴이 아니다.
interface X {
fun a()
fun b()
}
class Y(val x: X)
fun main() {
Y(object : X {
override fun a() {
TODO("Not yet implemented")
}
override fun b() {
TODO("Not yet implemented")
}
})
}
'책 정리 > Kotlin IN ACTION' 카테고리의 다른 글
3. 함수 정의와 호출 (0) | 2021.08.29 |
---|---|
2. 코틀린 기초 (0) | 2021.08.28 |
1. 코틀린이란 무엇이며, 왜 필요한가? (0) | 2021.08.28 |