코틀린 스터디

코틀린 - 클래스, 인터페이스, 상속

막이86 2023. 11. 10. 15:49
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