728x90
Kotlin in Action 을 요약한 내용입니다.
클래스
- 클래스와 인터페이스는 자바와는 약간 다름
- 인터페이스에 프로퍼티 선언이 들어갈 수 있다.
- 선언은 기본적으로 final 이며 public
- 중첩 클래스에는 외부 클래스에 대한 참조가 없다
- 클래스를 data로 선언하면 컴파일러가 표준 메소드를 생성
클래스 초기화
- 주 생성자와 초기화 블록
- constructor: 주 생성자나 부 생성자 정의
- init: 초기화 블록
class User constructor(_nickname: String) { // 주 생성자 val nickname: String init { // 초기화 블록 nickname = _nickname } }
- 부 생성자
- 부 생성자는 필요에 따라 여러개 선언 가능
open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributesSet) { } }
추상 클래스
- abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다
- 추상 멤버 앞에 open 변경자를 명시할 필요 없음
- animate 메소드는 open 필요 없음
- stopAnimating, animateTwice는 추상 멤버가 아님
- open을 명시하지 않으면 오버라이드 허용 안됨
abstract class Animated { abstract fun animate() open fun stopAnimating() { println("stopAnimating") } fun animateTwice() { println("animateTwice") } } class Ani: Animated() { override fun animate() { println("animate") } } fun main() { val a = Ani() a.animate() a.stopAnimating() a.animateTwice() }
클래스 내에서 상속 제어 변경자
- 클래스와 메소드는 기본값은 final
변경자 변경자가 붙은 멤버 설명
final | 오버라이드 할 수 없음 | 클래스 멤버의 기본 변경자 |
open | 오버라이드 할 수 있음 | 반드시 open을 명시해야 오버라이드 할 수 있다. |
abstract | 반드시 오버라이드해야 함 | 추상 클래스의 멤버에만 변경자를 붙일 수 있다. 추상 멤버에는 구현이 있으면 안된다. |
override | 상위 클래스나 상위 인스턴스의 멤버를 오버라이드 하는 중 | 오버라이드하는 멤버는 기본적으로 열려있다. 하위 클래스의 오버라이드를 금지하려면 final을 명시해야 한다. |
가시성 변경자
- 클래스 외부 접근을 제어
- 기본 가시성은 public
가시성 변경자
변경자 클래스 멤버 최상위 선언
public | 모든 곳에서 볼 수 있다. | 모든 곳에서 볼 수 있다. |
internal | 같은 모듈 안에서만 볼 수 있다. | 같은 모듈 안에서만 볼 수 있다. |
protected | 하위 클래스 안에서만 볼 수 있다. | 최상위 선언에 적용할 수 없음 |
private | 같은 클래스 안에서만 볼 수 있다. | 같은 파일 안에서만 볼 수 있다. |
- 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있음
- 코틀린에서는 안됨
- public 함수인 giveSpeesh 안에서 가시성이 더 낮은 타입인 talkativeButton을 참조 못함
internal open class TalkativeButton: Focusable { private fun yell() = println("Hey!!") protected fun whisper() = println("Let's talk!") } fun TalkativeButton.giveSpeech() { // 오류 yell() // 오류: yell 에 접근 안됨 whisper() // 오류: protected 멤버 }
인터페이스
- 인터페이스 안에는 추상 메소드 뿐 아니라 구현이 있는 메소드도 정의 가능
- 자바 8의 디폴트 메소드와 비슷
- 인터페이스에는 아무런 상태도 들어갈 수 없다.
- 인터페이스 선언
- interface Clickable { fun click() }
- 인터페이스 구현
- 클래스 이름 뒤에 콜론(:)을 붙이고 인터페이스 이름을 적는다
- 인터페이스를 원하는 만큼 개수 제한 없이 사용 가능
- override 인터페이스에 있는 프로퍼티나 메소드를 오버라이드
class Button: Clickable { override fun click() = println("I was clicked") } fun main() { Button().click() }
- 인터페이스 메소드의 디폴트 구현
- interface Clickable { fun click() fun showOff() = println("I'm clickable!") }
- 같은 이름의 메소드가 있는 인터페이스를 상속 받는 경우
- showOff 메소드를 오버라이딩 해야한다
- showOff를 구현하지 않으면 컴파일 오류 발생
- Class 'Button' must override public open fun showOff(): Unit defined in Clickable because it inherits multiple interface methods of it
interface Clickable { fun click() fun showOff() = println("I'm clickable!") } interface Focusable { fun setFocus(b: Boolean) = println("focus") fun showOff() = println("I'm focusable!") } class Button: Clickable, Focusable { override fun click() = println("I was clicked") override fun showOff() { // 구현하지 않으면 컴파일 에러 super<Clickable>.showOff() super<Focusable>.showOff() } } fun main() { Button().showOff() }
자바에서 코틀린의 메소드가 있는 인터페이스 구현
- 코틀린은 자바 6와 호환되도록 설계됨
- 인터페이스의 디폴트 메소드를 지원하지 않음
- 디폴트 메소드가 있는 인터페이스를 일반 인터페이스와 디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합
상속
기본적으로 final
- 클래스와 메소드는 기본적으로 final
- 상속이나 메소드를 오버라이드를 허용하고 싶으면 open 변경자 추가
- open class RichButton: Clickable { fun disable() {} open fun animate() {} override fun click() {} }
- 오버라이드를 금지하려면 final을 추가
- open class RichButton: Clickable { final override fun click() {} }
데이터 클래스
객체의 동등성
- 코틀린에서 ==연산자는 참조 동일성을 검사히지 않음
- 객체의 동등성을 검사(== 연산은 equals를 호출하는것과 같다)
- class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode" } fun main() { val client1 = Client("오현석", 4122) val client2 = Client("오현석", 4122) println(client1 == client2) // false }
equals 오버라이드
- is 검사는 자바의 instanceof와 같다
- 복잡한 작업 수행시 제대로 작동하지 않을 수 있음
- hashCode 정의 없기 때문에
class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode" override fun equals(other: Any?): Boolean { if (other == null || other !is Client) { return false } return name == other.name && postalCode == other.postalCode } } fun main() { val client1 = Client("오현석", 4122) val client2 = Client("오현석", 4122) println(client1 == client2) // true }
해시 컨테이너
- equals가 true를 반환하는 두 객체는 반드시 같은 hashCode를 반환 해야한다
- HashSet은 원소를 비교할 때 비용을 줄이기 위해 객체의 해시 코드를 비교하고 같은 경우에만 실제 값을 비교
- class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode" override fun equals(other: Any?): Boolean { if (other == null || other !is Client) { return false } return name == other.name && postalCode == other.postalCode } } fun main() { val processed = hashSetOf(Client("오현석", 4122)) println(processed.contains(Client("오현석", 4122))) // false }
- Client에 hashCode 구현
- class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode" override fun equals(other: Any?): Boolean { if (other == null || other !is Client) { return false } return name == other.name && postalCode == other.postalCode } override fun hashCode(): Int = name.hashCode() * 31 + postalCode } fun main() { val processed = hashSetOf(Client("오현석", 4122)) println(processed.contains(Client("오현석", 4122))) }
모든 클래스가 정의해야 하는 메소드 자동 생성
- data라는 변경자를 클래스 앞에 붙이면 필요한 메소드를 컴파일러가 자동으로 생성
- 인스턴스 비교를 위한 equals
- 해시 기반 컨테이너에 키로 사용할 수 있는 hashCode
- 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString
- data class Client(val name: String, val postalCode: Int)
데이터 클래스와 불변성
- 데이터 클래스를 분변 클래스로 만들기를 권장
- 불변 객체를 사용하면 스레드에서 사용하기 좋음
object 키워드: 클래스 선언과 인스턴스 생성
- object 키워드를 사용하는 상황
- 객체 선언은 싱글턴 정의하는 방법 중 하나
- 동반 객체는 팩토리 메소드를 담을 때 쓰임
- 객체 식은 자바의 무명 내부 클래스 대신 사용
- 객체 선언: 싱글턴을 쉽게 만들기
- 클래스와 마찬가지로 객체 선언 안에 프로퍼티, 메소드, 초기화 블록 등이 사용 가능
- 생성자는 객체 선언에 사용할 수 없음
- 객체 선언에 사용한 이름 뒤에 마침표를 붙이면 객체에 속한 메소드나 프로퍼티에 접근
data class Person(val name: String) object Payroll { val allEmpoyees = arrayListOf<Person>() fun calculateSalary() { for (person in allEmpoyees) { println(person) } } } fun main() { Payroll.allEmpoyees.add(Person("테스트")) Payroll.calculateSalary() }
- 클래스 안에서 객체 선언
- 객체도 인스턴스는 단 하나
data class Person(val name: String) { object NameComparator: Comparator<Person> { override fun compare(p1: Person, p2: Person): Int = p1.name.compareTo(p2.name) } } fun main() { val persons = listOf(Person("Bob"), Person("Alice")) println(persons.sortedWith(Person.NameComparator)) }
동반 객체: 팩토리 메소드와 정정 멤버가 들어갈 장소
- 코틀린 언어는 자바의 static 키워드를 지원하지 않음
- 패키지 수준의 최상위 함수와 객체 선언을 활용 가능
- 클래스 안에 companion 특별한 표시를 붙이면 클래스의 동반 객체로 사용 가능
- class A { companion object { fun bar() { println("Companion object call") } } } fun main() { A.bar() }
동반 객체를 일반 객체처럼 사용
- 동반 객체는 클래스 안에 정의된 일반 객체
- 동반 객체에 이름을 붙이거나, 인터페이스를 상속하거나, 확장 함수와 프로퍼티를 정의할 수 있다
- class Person(val name: String) { companion object Loader { fun fromJSON(jsonText: String): Person = ... } } fun main() { val person1 = Person.Loader.fromJSON("{name: 'Dmitry'}") person1.name val person2 = Person.Loader.fromJSON("{name: 'Brent'}") person2.name }
- 동반 객체에서 인터페이스 구현
- interface JSONFactory<T> { fun fromJSON(jsonText: String): T } class Person(val name: String) { companion object: JSONFactory<Person> { fun fromJSON(jsonText: String): Person = .... } }
객체 식: 무명 내부 클래스를 다른 방식으로 작성
- 무명 객체를 정의할 때 object 키워드를 사용
- window.addMouseListener(object: MouseAdapter() { override fun mouseClicked(e: MouseEvent) { } override fun mouseEnterd(e: MouseEvent) { } }) fun main() { val listener = object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { } override fun mouseEnterd(e: MouseEvent) { } } }
Aggregate
728x90
'코틀린 스터디' 카테고리의 다른 글
채널 (0) | 2023.12.04 |
---|---|
코루틴 스코프 만들기 (1) | 2023.11.24 |
코루틴 기반 동시성 프로그래밍 (0) | 2023.11.24 |
코틀린 - 예외처리, Type System, 컬렉션 (0) | 2023.11.10 |
코틀린 - 변수, 연산자, 반복문, 함수 (1) | 2023.11.10 |