Classes and Inheritance(클래스와 상속)

이미지 출처: https://kotlinlang.org/assets/images/open-graph/kotlin_250x250.png

Kotlin 클래스와 상속

kotlinlang 사이트의 가이드문서를 기반으로 포스팅 되었음을 밝힘니다.
Classes and Objects > Classes and Inheritance
출처: https://kotlinlang.org/docs/reference/classes.html
샘플코드: https://github.com/hanjoongcho/kotlin-cookbook

Table of Contents

클래스

1
2
3
4
5
6
7
// 코틀린의 클래스는 class 키워드를 사용하여 선언합니다.
class Invoice {
}

// 클래스 선언은 클래스 이름, 클래스 헤더(타입 파라미터, 기본 생성자 등 지정) 및 중괄호로 묶인 클래스 본문으로 구성됩니다.
// 클래스의 헤더와 본문은 선택 사항이며 클래스에 본문이 없으면 중괄호를 생략 할 수 있습니다.
class Empty

primary 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Kotlin의 클래스는 기본 생성자와 하나 이상의 secondary 생성자를 가질 수 있습니다.  
// 기본 생성자는 클래스 헤더의 일부분(타입 파라미터 포함)이며 클래스 이름을 따라갑니다.
class Person constructor(firstName: String) {
}

// 기본 생성자에 annotation 또는 visibility modifier가 없는 경우 constructor 키워드를 생략 할 수 있습니다.
class Person(firstName: String) {
}

// 기본 생성자에는 코드가 포함될 수 없습니다.
// 초기화 코드는 init 키워드로 시작되는 initializer block에 위치 할 수 있습니다.
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}

// 기본 생성자의 매개 변수는 initializer block에서 사용할 수 있습니다.
// 또한 클래스 본문에 선언된 속성의 초기화에도 사용할 수 있습니다.
class Customer(name: String) {
val customerKey = name.toUpperCase()
}

// 사실, 속성을 선언하고 기본 생성자에서 속성을 초기화하기 위해 Kotlin은 다음과 같은 syntax를 제공하고 있습니다.
// 일반 프로퍼티와 동일하게 기본 생성자에 선언하는 프로퍼티 역시 mutable(var) 또는 real-only(val) 형태로 선언할 수 있습니다.
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}

// 생성자에 annotation 또는 visibility modifier가 있는경우 생성자 키워드가 필요하며, 그 앞에 annotation 또는 visibility modifier를 선언합니다.
class Customer public @Inject constructor(name: String) { ... }

secondary 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 클래스에는 constructor 접두어가 붙은 secondary 생성자도 선언 할 수 있습니다.
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}

// 클래스에 primary 생성자가 있으면 secondary 생성자는 primary 생성자에 직접 또는 간접적으로 위임하여야 합니다.
// 동일한 클래스의 다른 생성자에 대한 위임은 this 키워드를 사용하여 수행합니다.
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}

// 추상클래스가 아닌경우 생성자를(primary or secondary)를 선언하지 않으면 인수가없는 primary 생성자를 갖습니다.
// 생성자의 접근범위는 public입니다.
// 클래스에 public 생성자가 없도록하려면 비어있는 private 생성자를 선언해야합니다.
class DontCreateMe private constructor () {
}

// NOTE: JVM에서 primary 생성자의 모든 매개 변수에 기본값이 있으면 컴파일러에서 기본으로 사용할 추가 매개 변수없는 생성자를 생성합니다.
// 따라서 Kotlin을 매개 변수없는 생성자를 통해 클래스 인스턴스를 만드는 Jackson이나 JPA와 같은 라이브러리와 함께 사용하는 것이 더 쉽습니다.
class Customer(val customerName: String = "")

클래스의 인스턴스 만들기

1
2
3
4
5
// 인스턴스를 생성하려면 클래스를 일반함수처럼 호출하면 됩니다.  
// Kotlin은 new 키워드를 사용하지 않습니다.
val invoice = Invoice()

val customer = Customer("Joe Smith")

상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Kotlin의 모든 클래스는 슈퍼클래스인 Any를 소유합니다.  
// Any는 java.lang.Object가 아닙니다.
// 특히 equals (), hashCode () 및 toString () 이외의 멤버가 없습니다.
class Example // 암묵적으로 Any를 상속

// 명시적으로 supertype을 선언하려면 클래스 헤더에 콜론과 함께 유형을 위치시킴니다.
// 클래스에 primary 생성자가 있으면 primary 생성자의 매개 변수를 사용하여 supertype을 바로 초기화 할 수 있습니다.
// open annotation은 Java의 final과 반대입니다.
// Kotlin의 모든 클래스는 final이므로 open annotation이 선언되어야 다른 클래스에서 이 클래스를 상속받을 수 있습니다.
open class Base(p: Int)

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

// 클래스에 기본 생성자가 없으면 각 secondary 생성자는 super 키워드를 사용하여 supertype을 초기화하거나 이를 수행하는 다른 생성자에 위임해야합니다.
// 이 경우, primary 생성자는 supertype의 다른 생성자를 호출 할 수 있습니다.
class MyView : View {
constructor(ctx: Context) : super(ctx)

constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

메소드 오버라이딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 그리고 Java와는 달리 Kotlin은 오버라이딩 할 수 있는 멤버에 대한 명시적인 annotation(코틀린에서는 이것이 open이라는 키워드로 불림)을 요구합니다.  
// 오버라이딩 구현시 override annotation을 사용해야 하며 그렇지 않으면 컴파일을 할 수 없습니다.
// Base.nv ()와 같은 함수에 open annotation이 없으면 서브 클래스에서 동일한 signature를 가진 메소드에 대한 선언이 금지됩니다.
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}

// override가 선언된 멤버는 자체적으로 열려 있습니다.
// 즉, 하위 클래스에서 재정의 될 수 있습니다.
// 하위클래스에서의 오버라이딩을 금지하려면 final을 사용하면 됩니다.
open class AnotherDerived() : Base() {
final override fun v() {}
}

프로퍼티 오버라이딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 프로퍼티 오버라이딩은 메서드 오버라이딩과 비슷한 방식으로 작동합니다.  
// 파생클래스에서 선언되는 프로퍼티는 override로 시작되어야 하며, 호환가능한 타입이어야 합니다.
// 선언된 각 프로퍼티는 initializer 또는 getter method와 함께 재정의 될 수 있습니다.
open class Foo {
open val x: Int get() { ... }
}

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

// var프로퍼티는 val프로퍼티로 재정의 할 수도 있지만 그 반대는 불가합니다.
// 이는 val프로퍼티가 기본적으로 getter method를 선언하고 var로 재정의되면 파생클래스에서 setter method를 추가로 선언하기 때문에 허용됩니다.
// 기본 생성자에서 속성 선언의 일부로 override 키워드를 사용할 수 있습니다.
interface Foo {
val count: Int
}

class Bar1(override val count: Int) : Foo

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

superclass 구현 후 호출하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 파생 클래스에서는 super 키워드를 사용하여 superclass 함수 및 속성을 호출 할 수 있습니다.  
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
}

// innerclass 내부에서 외부 클래스의 superclass 액세스하는 것은 외부 클래스 이름으로 수식 된 super 키워드(super@Outer)를 이용하면 됩니다.
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: Int get() = 0

inner class Baz {
fun g() {
super@Bar.f() // Calls Foo's implementation of f()
println(super@Bar.x) // Uses Foo's implementation of x's getter
}
}
}

오버라이딩 규칙

Kotlin에서 상속은 다음 규칙에 의해 규제됩니다.
클래스가 직접 superclass에서 동일한 멤버의 많은 구현을 상속하는 경우 해당 멤버를 재정의하고 자체 구현을 제공해야합니다(상속 된 클래스 중 하나를 사용하는 경우).
supertype을 나타내기 위해서는 꺽쇠괄호(super) 안에 supertype을 선언하면됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// A와 B 모두에서 상속하는 것이 좋습니다.  
// a(), b()의 경우 상속받은 클래스 안에 하나의 함수만 존재하므로 문제가 되지 않습니다.
// 그러나 f ()의 경우 C에 의해 상속 된 동일한 이름의 함수가 존재하므로 C에서 f()를 재정의하고 모호성을 제거해야 합니다.
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}

interface B {
fun f() { print("B") } // interface members are 'open' by default
fun b() { print("b") }
}

class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}

추상클래스

클래스와 멤버 중 일부는 abstract로 선언 될 수 있습니다.
abstract 멤버는 클래스 내에 구현되지 않습니다.
abstract가 아닌 멤버를 abstract 멤버로 재정의 할수도 있습니다.

1
2
3
4
5
6
7
open class Base {
open fun f() {}
}

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

Companion Objects

Kotlin에서는 Java 또는 C#과 달리 클래스에 정적 메소드가 없습니다.
대부분의 경우 단순히 패키지수준의 함수를 사용하는것이 좋습니다.
클래스 인스턴스가 없어도 호출 할 수 있지만 클래스의 내부(예: 팩토리 메소드)에 액세스해야하는 함수를 작성해야하는 경우 해당 클래스 내에 오브젝트 선언의 멤버로 작성할 수 있습니다.
보다 구체적으로, 클래스 내에 Companion Object를 선언하면 클래스 이름만 한정자로 사용하여 Java/C#에서 정적 메소드를 호출하는 것과 동일한 구문으로 멤버를 호출 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main(args: Array<String>) {
println(Commons.callCompanion())
}

class Commons() {
companion object {
fun callCompanion(): String {
return "Hello Companion Object"
}
}
}
--------------------------------------------------------
Hello Companion Object

Table of contents


Open source project on GitHub


Jupyter/IPython Notebook


Open API


NoSQL


Jekyll


Gradle


Kotlin


C++

  • C++ keywords
  • 윤성우 열혈강의 C++ 프로그래밍 예제코드 [펼치기]
    1. C언어 기반의 C++ 1
      • printf와 scanf를 대신하는 입출력 방식
      • 함수 오버로딩(Function Overloading)
      • 매개변수의 디폴트 값(Default Value)
      • 인라인 함수(Inline)함수
      • 이름공간(namespace)에 대한 소개
    2. C언어 기반의 C++ 2
      • 새로운 자료형 bool
      • 참조자(Reference)의 이해
      • 참조자(Reference)와 함수
      • maloc & free를 대신하는 new & delete
      • C++에서 C언어의 표준함수 호출하기
    3. 클래스의 기본
      • C++에서의 구조체
      • 클래스(Class)와 객체(Object)
      • 객체지향 프로그래밍의 이해
    4. 클래스의 완성
      • 정보은닉(Information Hiding)
      • 캡슐화(Encapsulation)
      • 생성자(Constructor)와 소멸자(Destructor)
      • 클래스와 배열 그리고 this 포인터
    5. 복사 생성자
      • ‘복사 생성자’ 와의 첫 만남
      • ‘깊은 복사’와 ‘얕은 복사’
      • 복사 생성자의 호출시점
    6. friend와 static 그리고 const
      • const와 관련해서 아직 못다한 이야기
      • 클래스와 함수에 대한 friend 선언
      • C++에서의 static
    7. 상속(Inheritance)의 이해
      • 상속에 들어가기에 앞서
      • 상속의 문법적인 이해
      • protected 선언과 세 가지 형태의 상속
      • 상속을 위한 조건
    8. 상속과 다형성
      • 객체 포인터의 참조관계
      • 가상함수(Vitual Function)
      • 가상 소멸자와 참조자의 참조 가능성
    9. 가상(Virtual)의 원리와 다중상속
      • 멤버함수와 가상함수의 동작원리
      • 다중상속(Multiple Inheritance)에 대한 이해
    10. 연산자 오버로딩 1
      • 연산자 오버로딩의 이해와 유형
      • 단항 연산자의 오버로딩의
      • 교환법칙 문제의 해결
      • cout, cin 그리고 endl의 정체
    11. 연산자 오버로딩 2
      • 반드시 해야 하는 대입 연산자의 오버로딩
      • 배열의 인덱스 연산자 오버로딩
      • 그 이외의 연산자 오버로딩
    12. String 클래스의 디자인
      • C++ 표준과 표즌 string 클래스
      • 문자열 처리 클래스의 정의
    13. 템플릿(Template) 1
      • 템플릿(Template)에 대한 이해와 함수 템플릿
      • 클래스 템플릿(Class Temlpate)
    14. 템플릿(Template) 2
      • Chapter 13에서 공부한 내용의 확장
      • 클래스 템플릿의 특수화(Class Temlpate Specialization)
      • 템플릿의 인자
      • 템플릿과 static
    15. 예외처리(Exception Handling)
      • 예외상황과 예외처리의 이해
      • C++의 예외처리 메커니즘
      • Stack Unwinding(스택 풀기)
      • 예외상황을 표현하는 예외 클래스의 설계
      • 예외처리와 관련된 또 다른 특성들
    16. C++의 형 변환 연산자
      • C++에서의 형 변환 연산

ETC