본문 바로가기

Kotlin

[Kotlin] 3.Interface, Class, 변경자, 초기화블록, 추상 프로퍼티

#Interface

  • 추상 메소드 뿐만아니라 구현체 있는 메소드도 정의할 수 있다. (like 'default method' in JAVA 8)
  • 상태 저장은 불가하다
//JAVA에서의 default method 예시
interface InterfaceTest { 
    default void print() { 
    	System.out.println("This is Default Method"); 
    }
}

 

코틀린 Interface 사용법

  • override  변경자 꼭 붙여야 함
  • 구현 메소드의 경우 오버라이드 안하고 그냥사용 할 수 있다(원한다면 override도 가능)
interface Human {
    fun sleep() //일반 추상 메소드 -> 당연히 오버라이드 해야 함
    fun speak() = println("I'm Human!!!!") //Default 구현이 있는 메소드 -> 오버라이드 안하고 그냥 사용 가능
}

class Student():Human {
    //오버라이딩 필수
    override fun sleep() {
         println("I sleep at 9pm")
    }
}

fun main(){
    val student = Student()
    student.sleep()  //Sleeping
    student.speak() //I'm Human!!!!   -> super의 speak()를 사용
}

 

Interface의 구현 메소드를 호출 할 때

  • 2개 이상의 Interface를 사용하는데 '생김새가 동일한 구현 메소드'가 각각의 Interface에 있을 경우 -> 오버라이드 하되, 어떤 super를 호출할 지 결정 할 수 있다.
interface Human { 
    fun speak() = println("I'm Human!!!!") // Woman 인터페이스의 speak()과 동일 메소드
}


interface Woman {
    fun speak() = println("I'm Woman!!!!")  // Human 인터페이스의 speak()과 동일 메소드
}

class Student() : Human, Woman {
    //오버라이드
    override fun speak() {
        super<Human>.speak() //호출할 인터페이스를 지정해서 super를 선택
        super<Woman>.speak() //호출할 인터페이스를 지정해서 super를 선택
    }
}

fun main() {
    val student = Student()
    student.speak()  //I'm Human!!!!
                     // I'm Woman!!!!
}

 

# 상속을 제어하는 변경자 open, final, abstract

  • 코틀린의 클래스와 메소드는 기본적으로 final이라 상속이 불가하다(메소드나 프로퍼티의 경우 '오버라이드'가 불가)
  • 상속을 허용하고자 하면 클래스 앞에 'open' 변경자 추가(오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 'open' 변경자 추가)

 

  • 하지만 Interface와 Abstract Class에서 final이 없는 메소드나 프로퍼티는 일단 'open'이다
  • + override 함수는 기본적으로 'open'이다 override 금지하고 싶으면 'final' 추가 (아래 예시에서 Student 클래스의 sleep 함수)

   [Interface]

  • Interface 멤버는 항상 'open'이며 final로 변경 할 수 없음 (open 키워드는 생략)
  • Interface 의 추상 멤버는 추상이지만 별도로 abstract 키워드 안붙여도 됨

   [Abstract Class]

  • abstract 클래스의 추상 멤버는 항상 'open'이며 final로 변경 할 수 없음 (open는 키워드 생략)
  • abstract 클래스의 비추상 멤버(abstract 키워드 없는) 는 기본 'final' 이지만  'open'으로 변경 가능

 

Interface 예제

interface Human {
    fun sleep() //일반 추상 메소드 -> 당연히 오버라이드 해야 함 ->  'open'
}

open class Student() : Human { //'open' 변경자로 클래스 상속 허용
    override fun sleep() { //override 함수 이므로 기본적으로 'open', but override 막고 싶으면 final 변경자 추가
        println("I sleep at 9pm")
    }
}

class HighSchoolStudent() : Student() {
    override fun sleep() { // 기본적으로 'open'인 Student클래스의 sleep 함수를 override
        println("I sleep at 11pm")
    }
}

 

Abstract Class 예제

abstract class Human {
    abstract fun sleep() //일반 추상 메소드 -> 당연히 오버라이드 해야 함 ->  'open'
    fun speak() = println("I'm Human!!!!") //Default 구현이 있는 메소드 -> 기본으로 'final'
}

open class Student() : Human() {
    //오버라이드 성공
    override fun sleep() {
        println("I sleep at 9pm")
    }

    //오버라이드 실패 = 컴파일 에러 = 'speak' in 'Human' is final and cannot be overridden'
    //override fun speak() {}
}

 

# 접근 제어 변경자 public, internal, protected, private

  • public : 모두 접근 가능, 변경자 생략시 기본 public
  • internal : 모듈 내부에서만 접근 가능
  • protected 
    1. 클래스 안에 선언 경우 : 하위 클래스에서만 접근 가능
    2. 최상위 선언의 경우 :  X... 불가  -> 최상위 선언에 protected 적용할 수 없음
  • private
    1. 클래스 안에 선언 경우 : 해당 클래스에서만 접근 가능
    2. 최상위 선언의 경우 : 해당 파일에서만 접근 가능 
  • + Java의 경우 변경자 생략 시 'package' 제한자인데 코틀린엔 그 개념X
  • + 확장 함수는 확장한 클래스의 private과 protected 멤버에  접근할 수 없지요

 

# 내부 클래스(Inner Class), 중첩 클래스(Nested Class)

  • 자바 - 클래스 안에 정의된 클래스는 Inner Class가 된다. 바깥 클래스에 대한 참조가 있다는 뜻인데, 이를 방지하려면 안에 정의된 클래스를 static으로 선언해야 한다.
  • 코틀린- 클래스 안에 정의 된 클래스는 Nested Class가 된다. 바깥 클래스에 대한 참조가 없다는 뜻인데, 참조하고자 한다면 안에 정의된 클래스에 inner 변경자를 추가해야 한다.
  • inner 변경자가 추가된 코틀랜 내부 클래스에서 바깥 클래스를 참조하는법  = this@바깥클래스명

# 봉인 클래스 (Sealed Class)

  • 상속할 하위 클래스들을 모두 상위 클래스 안의 클래스로만(중첩) 정의하게끔 제한하고 싶을 경우 사용하는 클래스다(코틀린 1.1 부터는 같은 파일 내 까지 허용)
  • 이렇게 제한할 경우 상위 클래스를 상속하고 있는 하위 클래스를 분명히 할 수 있어서 좋다
  • 클래스 선언에 'sealed' 표시를 추가하며
  • seald 클래스는 'open' 이다

 

# 주생성자, 부생성자, 초기화 블록

  • 클래스명 선언 뒤에 오는 생성자가 주 생성자 : 생성자 파람으로 프로퍼티 초기화 가능(var or val 추가)

가장 간단한 형태의 주 생성자

class Student(var name: String, var age: Int) {}
    보통 위와 같이 간단하게 쓰지만 이를 내부적으로 풀어쓰면 아래와 같다

풀어 쓰면

class Student constructor(nameParam: String, ageParam: Int) {
    var name: String
    var age: Int

    init {
        this.name = nameParam
        this.age = ageParam
    }
}
  • constructor 키워드는 생성자 정의시 사용(생성자가 기본형태면 constructor 키워드 생략 가능)
  • init 키워드는 초기화 블록을 의미 (주 생성자와 같이 사용됨)
  • 초기화 블록은 여러개 생성이 가능하고 여러개 일 시, 코드 순서대로 위에서 부터 실행 된다
  • 위의 예제 처럼 초기화 블록에서 프로퍼티 초기화만 한다면, 초기화 블록 쓰지 않고 '프로퍼티 초기화 식'을 사용 가능

프로퍼티 초기화 식 + constructor 키워드 생략

class Student(nameParam: String, ageParam: Int) { //constructor 키워드 생략
    var name = nameParam //프로퍼티 선언과 동시에 초기화
    var age = ageParam //프로퍼티 선언과 동시에 초기화

    init {
        println("name : " + name) //값이 표시됨
        println("age : " + age) //값이 표시됨
    }
}
  • 프로퍼티 초기화 식이나, 초기화 블록에서는 주 생성자의 파람만 사용할 수 있다
  • 부생성자의 파람은 init 블럭에서 사용할 수 없다 -> 왜? 부생성자의 경우엔 init 블럭보다 늦게 불림

생성자 파람에 Default 값 추가 가능 : 생성자 오버로딩 

class Student(var name: String, var age: Int =19) { //age 디폴트 값 추가
}


fun main() {
    val student18 = Student("name",18)
    val student = Student("name")
}

 

 

private 생성자 : 'private constructor' 추가

class Student private constructor(nameParam: String, ageParam: Int) {}

 

# 추상 프로퍼티 In Interface

  • Interface에 추상 프로퍼티를 선언하고, Interface를 구현하는 클래스들에게 해당 프로퍼티에 대한 정의를 override 하게끔 강제 할 수 있다

Human 인터페이스의 sleepTime 추상 프로퍼티

interface Human {
    val sleepTime: Int //'추상 프로퍼티' : Human을 구현할 하위 클래스는 무조건 override 해서 프로퍼티 구현해야함
}

class Student_01(override val sleepTime: Int) : Human {} //방법 1) 주 생성자에서 프로퍼티를 직접 선언 및 override

class Student_02(val age: Int) : Human {
    override val sleepTime: Int
        get() = if (age < 17) 10 else 6     //방법 2) 커스텀 게터
}

class Student_03(age: Int) : Human {
    override val sleepTime = getSleepTimeByAge(age) //방법 3) 프로퍼티 초기화 식
    private fun getSleepTimeByAge(age: Int) = if (age < 17) 10 else 6
}

방법 1) 가장 간단

방법 2) '커스텀 게터'를 구현한 방식으로, sleepTime 접근 할 때마다 if/else문 통해 값 리턴

방법 3) '프로퍼티 초기화 식'으로 객체 초기화 할 때, sleepTime 뒷받침 필드에 계산 및 저장해서 사용(상태 저장한다는 뜻)

 

즉, 프로퍼티 초기화가 복잡한 계산이 필요하고 비용이 들 경우, 방법 3)번으로 한번만 계산하고 상태값 저장하는것이 좋겠다

 


잘못된 내용이나 궁금한 내용 있으시면 댓글 달아주세요

좋은 하루 되세요!

 

출처 : Kotlin In Action - 에이콘 출판사

(위 도서를 학습하고 개인 학습용으로 정리한 내용입니다)

'Kotlin' 카테고리의 다른 글

[Kotlin] 3. Object  (0) 2021.11.15
[Kotlin] 3. Data Class, by  (0) 2021.11.07
[Kotlin] 2. 코틀린 기본 - 함수(2)  (0) 2021.11.02
[Kotlin] 2. 코틀린 기본 - 함수(최상위/확장)  (0) 2021.10.31
[Kotlin] 2. 코틀린 기본 - 맛보기  (0) 2021.10.18