가시성 변경자(visibility modifier): 기본적으로 공개
코트린에서는 아무 변경자도 없는 경우 선언은 모두 공개(public)이 된다. 코틀린에서는 패키지 전용(package-private)은 없다. 코틀린은 패키지를 네임스페이스를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
변경자 | 클래스 멤버 |
---|---|
public(기본 가시성) | 모든 곳에서 볼 수 있다. |
internal | 같은 모듈 안에서만 볼 수 있다.(모듈은 한꺼번에 컴파일되는 코틀린 파일들을 의미) |
protected | 하위 클래스 안에서만 볼 수 있다. |
private | 같은 클래스 안에서만 볼 수 있다. |
internal open class TalkativeButton: Focusable{
private fun yell() = println("Hey!")
protected fun whisper() = println("Let's talk!")
}
// 오류 : public 멤버가 자신의 "internal" 수신 타입인 "TalkatvieButton"을 노출함
fun TalkativeButton.giveSpeech(){
// 오류 : yell은 private 멤버임
yell()
// 오류 : whisper는 proteced 멤버임
// (proected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스 안에서만 보인다.)
// java는 같은 패키지 안에서 멤버에 접근할 수 있지만, 코틀린은 다르다.
whisper()
}
코틀린과 자바 가시성 규칙의 또 다른 차이는 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근할 수 없다는 점이다. 다음 토픽을 살펴보자.
내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스
클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나 코드 정의를 그 코드를 사용하느 곳 가까이에 두고 싶을 때 유용하다. 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 권한이 없다는 점이다.
View 요소를 하나 만든다. 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만 필요한 모든 데이터를 다른 도우미 클래스로 복사할 수는 있다. 이를 위해 state 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreSate메소드 선언이 있다.
interface State: Serializable
interface View{
fun getCurrentState(): State
fun getrestoreState(state: State){ }
}
Button 클래스의 상태를 저장하는 클래스는 Button 클래스 내부에 선언하면 편하다. 자바에서 그런 선언을 어떻게 하는지 살펴보자.
public class Button implements View{
@Override
public State getCurrentState(){
return new ButtonState();
}
@Override
public void restoreState(State state) { /*...*/ }
public class ButtonState implements State { /*...*/ }
}
이 코드로 버튼 직렬화를 하면 java.io.NotSerializableException: Button 오류가 생성된다.
자바에서 다른 클래스 안에 정의한 클래스는 자동으로 내부 클래스가 된다. 이 예제의 ButtonState 클래스는 바깥쪽 Button 클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해
ButtonState를 직렬화할 수 없다. Button을 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState의 직렬화를 방해한다. 이 문제를 해결하기 위해서는 ButtonState를 static 클래스로 선언해야 한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러싼 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.
코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 방금 설명한 것과 정반대다.
class Button: View{
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) { /*...*/ }
class ButtonState: State { /*...*/ } // 자바의 정적 중첩 클래스와 대응한다.
}
코틀린의 중첩 클래스에 아무런 변경자도 붙지 않으면 자바 static 중첩 클래스와 같다. 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner 변경자를 붙여야 한다.
클래스 B안에 정의된 클래스 A | 자바에서는 | 코틀린에서는 |
---|---|---|
중첩 클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음) | static class A | class A |
내부 클래스(바깥족 클래스에 대한 참조를 저장함) | class A | inner class A |
중첩 클래스 안에는 바깥쪽 클래스에 대한 참조가 없지만 내부 클래스에는 있다.
코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다.
Inner 안에서 Outer의 참조에 접근하려면 this@Outer라고 써야 한다.
class Outer{
inner class Inner{
fun getOuterReference(): Outer = this@Outer
}
}
봉인된 클래스: 클래스 계층 정의시 계층 확장 제한
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 -> // "else" 분기가 꼭 있어야 한다.
throw IllegalArgumentException("Unknown expression")
}
코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 꼭 디폴트 분기인 else 분기를 덧붙이게 강제한다. 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도 컴파일러가 when이 모든 경우를 처리하는지 제대로 검사할 수 없어서 실수를 할 수 있다.
sealed 클래스를 이용하면 아름답게 구현 가능하다.
상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위 클래스 정의를 제한할 수 있다. sealed 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩시켜야 한다.
// 기반 클래스를 sealed로 봉인한다.
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) {
// when 식이 모든 하위 클래스를 검사하므로 별도의 else 분기가 없어도 된다.
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
}
sealed로 표시된 클래스는 자동으로 open이다. 따라서 별도의 open 변경자를 붙일 필요가 없다. sealed 클래스의 상속 계층에 새로운 하위 클래스를 추가해도 when식이 컴파일되지 않는다. 따라서 when 식을 고쳐야 한다는 사실을 쉽게 알 수 있다
'프로그래밍 노트 > Kotlin' 카테고리의 다른 글
[Kotlin]인터페이스에 선언된 프로퍼티 구현/프로퍼티 게터 세터 (0) | 2020.11.30 |
---|---|
[Kotlin] 코틀린 생성자(주/부생성자) (0) | 2020.11.30 |
[Kotlin] 코틀린 인터페이스와 open, final, abstract 변경자 (0) | 2020.11.29 |
[Kotlin] 코틀린 로컬함수와 확장 (0) | 2020.11.24 |
[Kotlin] 코틀린 문자열과 정규식 다루기 (0) | 2020.11.24 |