#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
- 클래스 안에 선언 경우 : 하위 클래스에서만 접근 가능
- 최상위 선언의 경우 : X... 불가 -> 최상위 선언에 protected 적용할 수 없음
- private
- 클래스 안에 선언 경우 : 해당 클래스에서만 접근 가능
- 최상위 선언의 경우 : 해당 파일에서만 접근 가능
- + 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 |