[Kotlin] 3-1. 클래스 상속

클래스와 상속

코틀린에서 클래스는 class 키워드를 사용해서 선언

class Invoice {
}

클래스 선언은 클래스 이름과 클래스 헤더(타입 파라미터를 지정, 주요 생성자 등), 중괄호로
둘러 싼 클래스 몸체로 구성 / 헤더와 몸체는 선택적
클래스에 몸체가 없으면 중괄호 생략 가능

class empty

생성자

코틀린 클래스는 주요(primary) 생성자와 한 개 이상의 보조(secondary) 생성자를 가질 수 있다
주요 생성자는 클래스 헤더의 한 부분으로 클래스 이름(필요에 따라 타입 파라미터) 뒤에 위치

class Person constructor(firstName: String){
}

주요 생성자에 애노테이션이나 가시성 수식어가 없으면, constructor 키워드를 생략할 수 있다

class Person(firstName: String){
}

주요 생성자는 어떤 코드도 포함할 수 없다 / 초기화 블록에 초기화 코드가 위치할 수 있다
초기화 블록에는 init키워드를 접두사로 붙인다

class Customer(name: String) {
  init {
    logger.info("Customer initailized with value S{name}")
  }
}

초기화 블록에서 주요 생성자의 파라미터를 사용할 수 있다
주요 생성자 파라미터는 클래스 몸체에 선언한 프로퍼티 초기화에도 사용 가능

class Customer(name: String) {
  val customerKey = name.toUpperCase()
}

주요 생성자에서 포로퍼티를 선언하고 초기화할 수 있는 간결한 구문 제공

class Person(val firstName: String, val lastName: String, var age: Int) {
  // ...
}

일반 프로퍼티와 마찬가지로 주요 생성자에 선언한 프로퍼티는 수정가능(var)이거나 읽기 전용(val)
생성자가 애노테이션이나 가시성 수식어를 가지면 constructor 키워드 필요 / 키워드 앞에 수식어

class Customer public @Ingect constructor(name: String) { ... }

보조 생성자

클래스는 보조 생성자를 선언할 수 있다 / 보조 생성자는 constructor를 접두사로 붙인다

class Person {
  constructor(parent: Person) {
    parent.children.add(this)
  }
}

클래스에 주요 생성자가 있다면, 각 보조 생성자는 직접적으로 또는 다른 보조 생성자를 통해
간접적으로 주요 생성자를 호출해야 한다 / 같은 클래스의 다른 생성사를 호출할 때 this키워드 사용

class Person(val name: String) {
  constructor(name: String, parent: Person) : this(name) {
    parent.children.add(this)
  }
}

비추상 클래스에 어떤 생성자(주요 또는 보조)도 없으면, 인자가 없는 주요 생성자를 생성한다
이 생성자의 가시성은 public / public생성자를 원하지 않으면 기본 가시성이 아닌 빈 주요 생성자 선언해야함

class DontCreateMe private constructor() {
}
주의 : JVM에서 주요 생성자의 모든 파라미터가 기본 값을 가지면 컴파일러는 추가로 파라미터가 없는 생성자를 생성
 생성자는 기본 값을 사용하는 주요 생성자를 사용
class Customer(val customerName: String = "")

클래스의 인스턴스 생성

클래스의 인스턴스를 생성하려면 일반 함수와 같이 생성자 호출

val invoice = Invoice()
val customer = Customer("Joe Smith")

코틀린에는 new키워드가 없음

클래스 멤버

클래스는 다음을 가질 수 있다

class Example // 기본으로 Any를 상속

Any는 java.lang.Object가 아니다
Any는 equals(), hashCode(), toString()외에 다른 멤버를 갖지 않는다

상위타입을 직접 선언하려면 클래스 헤더에서 콜론 뒤에 타입을 위치시킨다

open class Base(p: Int)
class Derived(p: Int) : Base(p)

클래스에 주요 생성자가 있으면, 주요 생성자의 파라미터를 사용해서 기반 타입을 그 자리에서 초기화할 수 있음
클래스에 주요 생성자가 없으면, 각 보조 생성자에서 super키워드로 기반 타입 초기와 or
그걸 하는 다른 생성자 호출해야함

class MyView: View {
  constructor(ctx: Context) : super(ctx)
  constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

open애노테이선은 자바의 final과 반대이다. open은 다른 클래스가 이 클래스를 상속할 수 있게 허용
기본으로 코틀린의 모든 클래스는 final이다

메서드와 오버라이딩

코틀린은 메서드 오버라이딩을 명시적으로 해야 한다
자바와 달리 코틀린에서는 오버라이딩 가능한 멤버에 (open)애노테이션을 명시적으로 설정해야함

open class Base{
  open fun v() {}
  fun nv() {}
}
class Derived() : Base() {
  override fun v() {}
}

Derived.v()에는 override애노테이션 필요 / 이를 누락하면 컴파일에 실패
Base.nv()처럼 open애노테이션이 없는 경우, override를 사용하든 안 하든 하위 클래스에서 동일 시그니처를
갖는 메서드를 선언할 수 없다
final클래스(ex, open애노테이션이 없는 클래스)는 open 멤버를 가질 수 없다

override가 붙은 멤버는 그 자체가 open이며, 하위 클래스에서 다시 오버라이딩하는 것을 막고 싶다면 final사용

open class AnotherDerived : Base() {
  final override fun v() {}
}

프로퍼티 오버라이딩

프로퍼티 오버라이딩은 메서드 오버라이딩과 유사하게 동작
상위클래스에 선언된 프로퍼티를 하위 클래스에 재선언하려면 override를 사용하며 호환되는 타입 사용해야함
선언한 프로퍼티는 initializer를 가진 프로퍼티나 getter메서드를 가진 프로퍼티로 오버라이딩 할 수 있다

open class Foo {
  open val x: Int get() { ... }
}

class Bar1 : Foo() {
  override val x : Int = ...
}

또한 val 프로퍼티를 var 프로퍼티로 재정의할 수 있지만 반대는 안 된다
그 이유는 var 프로퍼티는 getter메서드를 선언하는데 그 것을 var로 오버라이딩하면 추가로
하위 클래스에서 setter메서드를 선언하기 때문이다

주요 생성자에 선언한 프로퍼티에도 override키워드를 사용할 수 있다

interface Foo {
  val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
  override var count: Int = 0
}

상위클래스 구현 호출

하위클래스는 super키워드를 사용해서 상위클래스의 함수와 프로퍼티에 접근 구현을 호출할 수 있다

open class Foo {
  open fun f() {println("Foo.f()")}
  open val x: Int get() = 1
}

class Bar : Foo() {
  override fun f() {
    super.f()
    println("Bar.f()")
  }
  override val x: Int get() = super.x + 1
}

내부 클래스는 super@Outer와 같이 외부 클래스의 이름을 사용해서 외부 클래스의 상위 클래스에 접근 가능

class Bar : Foo() {
  override fun f() { ... }
  override val x: Int get() = 0
  
  inner class Baz {
    fun g() {
      super@Bar.f()  // Foo의 f() 구현
      println(super@Bar.x)  // Foo의 x의 getter 사용
    }
  }
}

오버라이딩 규칙

코틀린의 구현 상속은 다음 규칙에 따라 제한
클래스가 바로 위의 상위 클래스에서 같은 멤버의 구현을 여러 개 상속받으면,
반드시 멤버를 오버라이딩하고 자신의 구현을 제공해야함
어떤 상위타입의 구현을 사용할지 선택하려면 혿화살괄호(<>)안에 상위 타입의 이름을 붙인 super를 사용

open class A {
  open fun f() { print("A") }
  fun a() { print("a") }
}

interface B {
  fun f() { print("B") }  // 인터페이스 멤버는 기본이 'open'이다
  fun b() { print("b") }
}

class C() : A(), B {
  // 컴파일러는 f()를 오버라이딩할 것을 요구함
  override fun f() {
    super<A>.f() // A.f() 호출
    super<B>.f() // B.f() 호출
  }
}

A와 B를 둘 다 상속해도 a()와 b()는 문제가 되지 않는다. C가 함수의 구현을 한 개만 상속하기 때문이다
하지만 f()의 경우 C는 두 개의 구현을 상속 받으므로 C에 f()를 오버라이딩하고 자신의 구현을 제공해야함

추상 클래스

클래스나 클래스의 멤버를 abstract로 선언할 수 있다
추상 멤버는 그 클래스에 구현을 갖지 않는다 / 추상 클래스나 함수에 open을 붙일 필요는 없다
추상이 아닌 open멤버를 추상 멤버로 오버라이딩할 수 있다

open class Base() {
  open fun f() {}
}

abstract class Derived : Base() {
  override abstract fun f()
}

컴페니언 오브젝트

코틀린은 자바와 달리 클래스에 정적 메스드가 없다
클래스 인스턴스 없이 클래스의 내부에 접근해야 하는 함수를 작성해야 한다면 그 클래스에 속한 오브젝트 선언의
멤버로 함수를 작성할 수 있다