본문 바로가기
Kotlin

Kotlin 기본 클래스(생성자,오버로딩,상속,오버라이딩,open,abstract,companion object, sealed class

by 봄석 2019. 1. 23.

Kotlin 기본 클래스

Kotlin의 기본 클래스를 정리해 보겠습니다



생성자


class ClassName(name:String){
}
 
 
생성자의 
class ClassName constructor(name: String) {

  // constructor 생략가능

=

constructor를 생략하여 간단하게 class ClassName(name:String)




초기화

//init 블럭에서의 초기화
class ClassName(name: String) {
  init {
    println("Initialized with value ${name}")
  }
}
 
 
//init 블럭을 사용하지 않고, 변수에 바로 넘겨 
class ClassName(name: String) {
  val upperName = name.toUpperCase()
}


init 블럭을 사용하는방법과 사용하지않고 바로 변수에 저장하는 방법이 있습니다.



1개 이상의 생성자

class Person(val name: String) {
  constructor(name: String, age: Int) : this(name) {
    // ...
  }
}

consturctor을 여러 개로 정의할 수 있는데 위와같은 형태로 constructor를 이용해 생성자 오버로딩을 할 수있습니다.

첫번째 생성바 val name:String

두번째 생성자 name:String, age:Int :this(name){ }


name은 중복적으로 사용되는 키워드이므로 기존 생성자로 넘겨주기 위해서 this() 키워드를 사용하여 정의 가능 합니다.



생성자 private로 사용하기

class PrivateConstructor private constructor() {
  // Class 정의
}

java 에서는 싱글톤을 사용하는 경우 private를 정의하여 생성자를 가립니다.

Kotlin에서도 private의 생성자를 만들수 있는데 다음과 같습니다.



Kotlin 상속

open class Base(age: Int)
 
// open으로 생성한 추상 클래스를 다음과 같이 사용

class UseBase(age: Int) : Base(age)

Kotlin에서는 abstract와 interface에 대한 별도 구분 없이 : 으로 구분합니다.


Android 에서 많이 사용하는 View상속은 아래와같이 처리할 수 있습니다.

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


함수 Overriding

함수를 Overriding을 하기 위해서 java에서는 abstract 또는 interface를 사용합니다

abstract는 extends을 이용하여 상속을 구현하고,interface은 implements을 이용하여 상속을 구현합니다.


Koltin에서는 Interface와 Abstract를 : 로 처리합니다.

open 이라는 키워드를 함께 사용하면 아래와 같습니다.

interface BaseItem {
    fun onStart()
}
 
open class Base {
    open fun v() {}
    fun nv() {}
}
 
class AbstractBase() : Base(), BaseItem {
    // Base의 open 키워드를 통해 상속을 구현
    override fun v() {}
    // Interface에서 구현한 상속
    override fun onStart() {}
}

Kotlin open

Kotlin에서 사용하게 되는 open 키워드는 다음과 같습니다.

  •  java에서는 상속의 재 정의를 방지하기 위해 final을 사용합니다.
  • Kotlin에서는 반대로 상속의 재정의를 허용하기 위해 open을 사용합니다.
즉, java에서는 final을 통해서 상속의 재 정의를 막지만 Kotlin에서는 open을 이용하여 함수의 재 정의를 할 수있도록 허용합니다.
만약 open 클래스의 open 함수가 있다하면 , 이는 상속을 받아 재정의가 가능한 형태가 제공됩니다.

그래서 아래와 같이 open 클래스를 구현하게 되면  v() 는 재정의가 가능하고 nv()는 재정의가 불가능한 형태가 만들어집니다.
open class Base {
    open fun v() {
        print("ABC")
    }
    fun nv() {}
}


open은 변수에서도 사용이 가능한데 아래와 같습니다.

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


아래 코드를 실행해보면 12라는 결과를 얻을 수 있습니다.
fun main(args: Array<String>) {
    print(C(12).x)
}
 
open class A {
    open val x: Int = 0
}
 
class C(override val x: Int) : A() {
}



Overriding Rules

아래의 코드에서 다중 상속을 허용하게 됩니다.

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()
  }
}


해당 c() 의 f() 함수를 실행한 결과는 아래와 같습니다.

•print(“A”), print(“B”)

open의 클래스A의 f() 함수와 B interface의 f() 함수가 다중으로 상속되는 상황입니다.

그래서 super<Base>의 형태로 함수를 각각 불러올 수 있습니다. 

class C()에서 보듯 f() 함수를 정의한 부분을 살펴볼 수 있습니다.


  • super<A>.f()는 A.f()를 하는 것과 동일합니다.
  • super<B>.f()는 B.f()를 하는 것과 동일합니다.
  • 함수 f()open class Af()도 상속받았고, interface Bf()도 함께 상속이 된 상태라서 가능한 rule입니다.

    추가로 Java 8 이전 버전을 공부하신 분은 interface에서 함수 정의가 가능함을 의아해하실 수 있을 것 같습니다. 다음의 java 8 virtual extension methods 자료를 참고하시면 되겠습니다.



    Abstract class
    kotlin의 추상클래스는 자바의 추상클래스(abstact)와 동일하게 구현됩니다.
    abstract class BaseUse(name: String) {
     
        init {
            updateName(name)
        }
     
        protected abstract fun updateName(name: String)
    }
     
     
    // Base를 상속 받는 클래스
    class UseName(name: String) : BaseUse(name) {
     
        override fun updateName(name: String) {
            // 정의
        }
    }


      추가로 Kotlin의 open class를 추가하여 아래와 같이 확장도 가능합니다.

      open class Base {
        open fun f() {}
      }
       
      abstract class AbstractBase : Base() {
        override abstract fun f()

      }


      open 클래스의 f() 함수는 override가 가능한 형태입니다.

      이를 AbstractBase에서 상속받고, abstract로 확장할 수 있습니다.



      Kotlin의 static - companion object

      // 생성자 private 처리
      class ClassName private constructor() {
       
        // 외부에서 static 형태로 접근 가능
        companion object {
          fun getInstance() = ClassName()
        }
      }
       
      // Use kotlin

      val className = ClassName().getInstance()



      Sealed Classes

      열거 형태로 자기 자신을 return이 가능하고 , 다음과 같이 class와 object에 자기 자신을 return하는 클래스 형태를 제공합니다.


      대략 다음을 실행하면 eval 이라는 함수에 Expr을 셋팅합니다. when으로 동작하는데 Expr의 Sum 클래스를 초기화 합니다.

      초기화 시에는 2개의 Exptr을 사용하고, 이를 +하는 함수입니다. 실제로는 expr.number을 가져와서 처리하는 예제입니다.

      fun main(args: Array<String>) {
          print(eval(Expr.Sum(Expr.Const(12.44), Expr.Const(12.33))))
      }
       
      sealed class Expr {
          class Const(val number: Double) : Expr()
          class Sum(val e1: Expr, val e2: Expr) : Expr()
          object NotANumber : Expr()
      }
       
      fun eval(expr: Expr): Double = when(expr) {
          is Expr.Const -> expr.number
          is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
          Expr.NotANumber -> Double.NaN
          // the `else` clause is not required because we've covered all the cases
      }




      ex) sealed와 중첩 클래스 : 모든 경우의 수에 대해 분기 처리하도록 강제, 실수 방지

      다음과 같은 경우 when에는 반드시 else(디폴트 분기)를 적어주어야 한다.
      이렇게 디폴트 분기를 사용하는 경우 Expr클래스 계층에 새로운 하위 클래스를 추가했을 때, when에 추가하는 것을 깜빡해도 디폴트 분기를 타게 되므로 프로그램이 예상대로 동작하는 것 처럼 보여 이를 잡아내기가 어려워질 수 있다. 
      interface Expr
      class Num(val value: Int) : Expr
      class Sum(val left: Expr, val right: Expr) : Expr
      
      fun eval(e: Expr): Int =
              when (e) {
                  is Num -> e.value
                  is Sum -> eval(e.right) + eval(e.left)
                  else -> throw IllegalArgumentException("Unknown expresssion")
              }
      
      이런 경우 sealed와 중첩 클래스를 사용하면 디폴트 분기를 사용하지 않고 모든 경우의 수를 처리하도록 강제할 수 있어 더 확실하게 검사할 수 있다.
      sealed class는 자동으로 open이다.

      sealed class Expr {
          class Num(val value: Int) : Expr()
          class Sum(val left: Expr, val right: Expr) : Expr()
      }
      
      fun eval(e: Expr): Int =
              when (e) {
                  is Expr.Num -> e.value
                  is Expr.Sum -> eval(e.right) + eval(e.left)
              }
      


      'Kotlin' 카테고리의 다른 글

      Kotlin List, Map  (4) 2019.01.23
      Kotlin enum과 when  (4) 2019.01.23
      Kotlin - Realm 사용하기  (2) 2019.01.07
      Kotlin - 소리 재생하기(MediaPlayer ,SoundPool 클래스, 버전분기)  (2) 2019.01.05
      Kotlin - 체인모드(ContraintLayout)  (2) 2019.01.05

      댓글