[인터페이스 특징]
interface Clickable {
fun click() // 일반 메소드 선언
fun showOff() = println("I'm clickable!"); // 디폴트 구현이 있는 메소드
}
// 동일한 메소드를 구현하는 다른 인터페이스 정의
interface Focusable {
fun setFocus(b: Boolean) = println("I ${if (b) "got" else "lost"} 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(args: Array<String>) {
val button = Button()
button.showOff() // I'm clickable! I'm focusable!
button.setFocus(true) // I got focus.
button.click() // I was clicked.
}
open class RichButton : Clickable { // 다른 클래스가 이 클래스 상속 가능
fun disable() {} // 하위 클래스에서 이 메소드 오버라이드 불가능
open fun animate() {} // 하위 클래스에서 이 메소드 오버라이드 가능
override fun click {} // 열려있는 메소드를 오버라이드, 오버라이드한 메소드는 기본적으로 열려있음
final override fun click2 {} // 하위 클래스에서 오버라이드하지 못하게 금지
}
abstract class Animated { // 이 클래스는 추상클래스, 클래스의 인스턴스 만들 수 없음
abstract fun animate() // 하위 클래스에서 반드시 오버라이드 해야함
open fun stopAnimating() {} // 추상 클래스에 속했더라도 비추상 함수는 open으로 오버라이드를 허용해야 함
}
[코틀린의 상속 제어 변경자]
가시성 변경자 : 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어
[코틀린의 가시성 변경자]
변경자 | 클래스 멤버 | 최상위 선언 |
public | 모든 곳에서 볼 수 있음 | 모든 곳에서 볼 수 있음 |
internal | 같은 모듈 안에서만 볼 수 있음 | 같은 모듈 안에서만 볼 수 있음 |
protected | 하위 클래스 안에서만 볼 수 있음 | (최상위 선언에 적용할 수 없음) |
private | 같은 클래스 안에서만 볼 수 있음 | 같은 파일 안에서만 볼 수 있음 |
클래스 B 안에 정의된 클래스 A | 자바에서는 | 코틀린에서는 |
중첩 클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음 | static class A | class A |
내부 클래스(바깥쪽 클래스에 대한 참조를 저장함) | class A | inner class A |
[내부 클래스 안에서 바깥쪽 클래스 참조에 접근]
class Outer {
inner class Inner {
fun getOuterReference() : Outer = this@Outer
}
}
sealed 변경자 : 상위 클래스에 붙이면 상위 클래스를 상속한 하위 클래스 정의를 제한 가능 -> sealed 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩시켜야 함
주 생성자 : 클래스 이름 뒤에 오는 괄호로 둘러싸인 코드
[주 생성자 코드들]
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) // val은 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻
[기반 클래스의 생성자 호출]
open class User(val nickname: String) {}
class TwitterUser(nickname: String) : User(nickname) {}
[클래스 외부에서 인스턴스화 금지하는 방법]
class Secretive private constructor() {}
open class View {
constructor (ctx: Context) {
// 부 생성자
}
constructor(ctx: Context, attr: Attribute) {
// 부 생성자
}
}
class MyButton : View {
// 상위 클래스의 생성자 호출
constructor(ctx: Context) : super(ctx) { }
constructor(ctx: Context, attr: Attribute) : super(ctx, attr) {}
}
// 자신의 다른 생성자 호출하는 경우
class MyButton : View {
constructor(ctx: Context) : this(ctx, MY_STYLE) {} // 이 클래스의 다른 생성자에게 위임
constructor(ctx: Context, attr: Attribute) : super(ctx, attr) {}
}
추상 프로퍼티 선언이 들어있는 인터페이스의 프로퍼티 구현
interface User { val nickname: String }
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) // 프로퍼티 초기화 식
}
세터에서 뒷받침하는 필드 접근하기
class User(val name: String) {
var address: String = "unspecitifed"
set(value: String) {
println(""" Address was changed for $name:
"$field" -> "$value".""".trimIndent()) // 뒷받침하는 필드 읽기
field = value // 뒷받침하는 필드 값 변경하기
}
}
val user = User("Alice")
user.address = "EE"
Address was changed for Alice: "unspecified" -> "EE"
기본적으로 접근자의 가시성은 프로퍼티의 가시성과 같음 -> get이나 set 앞에 가시성 변경자 추가해서 접근자 가시성 변경 가능
[비공개 세터가 있는 프로퍼티 선언하기]
class LengthCounter {
var counter: Int = 0
private set // 이 클래스 밖에서 이 프로퍼티의 값을 바꿀 수 없음
fun addWord(word: String) {
counter += word.length
}
}
val lengthCounter = LengthCounter()
lengthCounter.addWord("Hi !")
println(lengthCounter.counter) // 3
// Client 클래스의 초기 정의
class Client(val name: String, val postalCode: Int)
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
}
val Client1 = Client("오현석", 4122)
println(client1) // Cliene(name=오현석, postalCode=4122)
class Client(val name: String, val postalCode: Int) {
override fun equals(other: Any?) : Boolean {
if (other == null || other !is Client) return false
return name == other.name && postalCode == other.poastlCode
}
}
class Client(val name: String, val postalCode: Int) {
...
override fun hashCode() : Int = name.hashCode() * 31 + postalCode
}
=> 코틀린에서는 3가지 메소드를 자동으로 생성해줄 수 있음
data 변경자 : 클래스 앞에 붙이면 필요한 메소드를 컴파일러가 자동으로 생성해줌 => 이러한 클래스를 '데이터 클래스'
class Client(val name: String, val postalCode: Int) {
fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode)
}
val lee = Client("이계영", 4122)
println(lee.copy(postalCode = 4000))
// Client(name=이계영, postalCode=4000)
class CountingSet<T> ( val innerSet: MutableCollection<T> = HashSet<T> () ) : MutableCollection<T> by innerSet {
var objectsAdded = 0
override fun add(element: T) : Boolean {
objectsAdded++
return innserSet.add(element)
}
override fun addAll(c: Collection<T>): Boolean {
objectAdded += c.size
return innerSet.addAll(c)
}
}
val cset = CountingSet<Int>()
cset.addAll(listOf(1, 1, 2))
println("${cset.objectsAdded} objects were added, ${cset.size} remain")
// 3 objects were added, 2 remain
객체 선언 : 클래스 선언 + 그 클래스에 속한 단일 인스턴스 선언
-> object 키워드로 시작
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person in allEmployees) {
...
}
}
}
Payroll.allEmployees.add(Person(...))
Payroll.calculateSalary()
// 중첩 객체를 사용해 Comparator 구현
data class Person(val name: String) {
object NameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person) : Int = p1.name.compareTo(p2.name)
}
}
val persons = listOf(Person("Bob"), Person("Alice"))
println(persons.sortedWith(Person.NameComparator))
// [Person(name=Alice), Person(name=Bob)]
동반 객체 : 클래스 안에 정의된 객체 중 하나에 companion이라는 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있음 -> 자바의 정적 메소드 호출이나 정적 필드 사용 구문과 같아짐
class A {
companion object {
fun bar() {
println("Companion object called")
}
}
}
A.bar() // Companion object called
class User private constructor(val nickname: String) { // 주 생성자를 비공개로 만듦
companion object {
// 팩토리 메소드
fun newSubscribingUser(email: String) = User(email.substringBefore('@'))
fun newFacebookUser(accountId: Int) = User(getFacebookName(accountId))
}
}
val subscribingUser = User.newSubscribingUser("bob@gmail.com")
val facebookUser = User.newFacebookUser(4)
println(subscribingUser.nickname) // bob
class Person(val name: String) {
companion object Loader {
fun fromJSON(jsonText: String) : Person = ...
}
}
person = Person.Loader.fromJSON("{name: 'Dmitry')")
person.name //Dmitry
interface JSONFactory<T> {
fun fromJSON(jsonText: String) : T
}
class Person (val name: String) {
companion object : JSONFactory<Person> {
override fun fromJSON(jsonText: String) : Person = ... // 동반 객체가 인터페이스를 구현
}
}
fun loadFromJSON<T>(factory: JSONFactory<T>) : T { }
loadFromJSON(Person) // 동반 객체의 인스턴스를 함수에 넘김
[동반 객체에 대한 확장 함수 정의하기]
class Person(val firstName: String, val lastName: String) {
companion object {}
}
fun Person.Companion.fromJSON(json:String) : Person { //확장함수 선언 }
val p = Person.fromJSON(json)
무명 객체 -> object 키워드 사용
[무명 객체로 이벤트 리스너 구현]
fun countClicks(window: Window) {
var clickCount = 0 // 로컬변수 정의
window.addMouseListener {
object : MouseAdapter() { // MouseAdapter를 확장하는 무명 객체 선언
override fun mouseClicked(e: MouseEvent) {
clickCount++ // 로컬 변수의 값 변경 가능
}
override fun mouseEntered(e: MouseEvent) {}
}
}