코틀린 인액션 4장 - 1
클래스 계층 정의
코틀린 인터페이스
- 인터페이스는 interface를 사용하여 정의한다.
- 추상 메소드와 구현이 있는 자바의 default 메소드 정의 가능하지만, 필드를 가질 수 없다.
interface Clickable {
fun click()
}
코틀린에서는 클래스 이름 뒤에 콜론(:)을 붙이고 인터페이스와 클래스 이름을 적는 것으로 클래스 확장과 인터페이스 구현을 모두 처리한다.
interface Clickable {
fun click()
}
class Button : Clickable {
override fun click() = println("Button clicked")
}
fun main(args: Array<String>) {
val button: Clickable = Button()
button.click() // Button clicked
}
인터페이스는 원하는 만큼 제한 없이 구현할 수 있지만, 클래스는 오직 하나만 상속받을 수 있다.
또한 코틀린에서는 자바와 달리 메소드를 오버라이드할 때 override 변경자를 꼭 사용해야 한다.
interface Clickable {
fun showOff() = println("I'm clickable")
}
class Button : Clickable {
}
fun main(args: Array<String>) {
val button: Clickable = Button()
button.showOff() // I'm clickable
}
인터페이스의 default 메소드는 자바와 달리 메소드 앞에 default 키워드를 붙일 필요없이 그냥 메소드를 구현하면 된다.
interface Clickable {
fun click()
}
interface Test {
fun click()
}
class Button : Clickable, Test {
override fun click() = println("Hello World")
}
fun main(args: Array<String>) {
val button: Clickable = Button()
button.click() // "Hello World"
}
만약 위처럼 한 클래스에서 두개의 인터페이스를 구현할 때, 두 인터페이스의 추상 메소드가 이름과 시그니처가 같다면 구현 클래스에서는 어느 인터페이스의 메소드를 구현하는 것일까?
컴파일러는 이름과 시그니처가 같으면 두 메소드를 동등하게 인식해서 구분하지 않는다.
interface Clickable {
fun showOff() = println("I'm focusable")
}
interface Test {
fun showOff() = println("I'm testable")
}
class Button : Clickable, Test {
}
fun main(args: Array<String>) {
val button: Clickable = Button()
button.showOff() // 컴파일 오류
}
그렇다면 위처럼 두 인터페이스의 default 메소드의 이름이 같다면 구현 클래스에서는 어느 showOff 메소드가 선택될까?
default 메소드라고해서 위와 같이 사용한다면 컴파일 오류가 발생한다. 이런 경우 showOff 메소드 구현을 대체할
오버라이딩 메소드를 제공해야 한다.
interface Clickable {
fun showOff() = println("I'm focusable")
}
interface Test {
fun showOff() = println("I'm testable")
}
class Button : Clickable, Test {
override fun showOff(){
super<Clickable>.showOff() // Clickable의 showOff()만 호출
super<Test>.showOff() // Test의 showOff()만 호출
// 혹은 둘 다 호출
// 혹은 새로운 구현
}
}
fun main(args: Array<String>) {
val button: Clickable = Button()
button.showOff() // 컴파일 오류
}
위와 같이 두 메소드를 아우르는 새로운 구현을 하위 클래스에 직접 구현해야 한다.
즉 위처럼 “super<인터페이스>.메소드()"로 상위 타입의 멤버 메소드를 직접 호출하는 식으로 showOff()를 구현해야 한다.인터페이스>
open, final, abstract 변경자: 기본적으로 final
- 자바에서는 final 키워드로 상속을 금지시킬 수 있다.
- 코틀린에서는 무분별한 상속을 막기위해 모든 클래스와 메소드는 기본적으로 final이다.
- 즉 상속에 기본적으로 닫혀있다.
- 어떤 클래스, 메소드, 프로퍼티의 상속을 허용하고 싶다면 앞에 open 변경자를 붙여야 한다.
open class RichButton { // open 변경자를 통해 상속이 가능해졌다.
fun disable() {} // 기본적으로 final이다. override 할 수 없다.
open fun animate() {} // open 변경자를 통해 override가 가능해졌다.
}
- 자신이 오버라이드하는 메소드를 하위 클래스에서 오버로이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야 한다.
open class RichButton : Clickable { // open 변경자를 통해 상속이 가능해졌다.
final override fun click() {...}
}
- 코틀린도 자바와 마찬가지로 추상 클래스를 abstract로 선언할 수 있다.
- 추상 멤버는 기본으로 열려있기 때문에 추상 멤버앞에 임의로 open을 명시할 필요가 없다. 아래의 예시를 보자.
abstract class Animated { // 추상 클래스 선언
abstract fun animate() // 추상 메소드 선언, 하위 클래스는 반드시 오버라이드해야 한다.
open fun stop() {} // 기본이 final이기 때문에 열리기 원한다면 open을 붙여준다.
fun run() {} // 오버라이드에 닫혀있다.
}
가시성 변경자: 기본적으로 공개
- 코틀린은 자바와 같은 public, protected, private 변경자가 있지만, 자바와 달리 default(지정없음)은 모두 public이다.
- 자바에서는 protected일 때 같은 패키지 및 하위 클래스에서 접근이 가능하지만, 코틀린에서는 하위 클래스에서만 접근이 가능하다.
- 즉 자바의 동일 패키지 내의 클래스로부터만 참조 가능한(패키지 프라이빗)은 없고, internal이라는 새로운 가시성 변경자를 제공한다.
- internal은 모듈(한 번에 한꺼번에 컴파일되는 코틀린 파일들) 내부에서만 볼 수 있음을 의미한다.
- IntelliJ IDEA 모듈
- Maven / Gradle 프로젝트
- 하나의 Ant 태스크 내에서 함께 컴파일되는 파일들
- 코틀린에서는 최상위 선언(클래스, 함수, 프로퍼티)에 대해 private 가시성을 허용해서 파일 내부에서만 사용을 가능하게 한다.
internal open class TalkativeButton : Focusable {
private fun yell() = println("hey")
protected fun whisper() = println("Let's talk")
}
fun TalkativeButton.giveSpeech() { // 1)
yell() // 2)
whisper() // 3)
}
- 위의 코드는 3가지의 컴파일 오류가 발생한다.
- 1)은 giveSpeech 함수는 public이기 때문에 그보다 가시성이 더낮은 internal 타입의 TalkativeButton를 접근할 수 없다.
- TalkativeButton를 public으로 변경 혹은 giveSpeech 함수를 internal 이하의 가기성 변경자로 변경해야 한다.
- 2)는 private 멤버에 접근할 수 없다.
- 3) protected는 하위 클래스(같은 패키지 X)에서만 접근할 수 있다.
- 클래스를 확장한 함수는 클래스의 protected나 private 멤버에 접근할 수 없다.
Comments