가시성 접근자(visibility modifier)는 클래스 또는 최상위 선언에 대한 접근을 제한함으로써 그 클래스 또는 파일에 의존하는 외부 코드를 꺠지 않고도 클래스 내부 또는 파일을 변경할 수 있다.
기본적으로 kotlin의 가시성 접근자는 java와 비슷하다. 자바와 같이 public, protected, private 변경자가 있다. 하지만 코틀린의 default 가시성은 java와 다르다. 만약에 아무언 변경자도 없으면 public이 된다.
또한 java의 기본 가시성인 패키지 전용(package-private)은 kotlin에 없다. kotlin은 package를 nameSpace를 관리하기 위한 용도로 사용한다. 그래서 package를 가시성 제어에 사용하지 않는다.
그러면 kotlin에서는 package 전용 가시성은 어떻게 관리하는가?
kotlin에서는 package 전용 가시성을 위해 internal이라는 새로운 가시성 변경자를 도입했다. (우리말로는 모듈 내부) internal은 모듈 내부에서만 볼 수 있음을 의미한다. 모듈은 한 번에 컴파일되는 코틀린 파일들을 의미한다. 인텔리J나 이클립스, 메이븐, 그레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 테스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
모듈 내부 가시성은 모듈 구현에 제대로된 캡슐화를 제공한다는 장점이 있다. java에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근할 수 있다. 그렇기 때문에 여기서 문제가 발생하는 데, 그 문제는 OOP언어의 특징인 캡슐화가 깨질 수 있다는 것이다.
또 다른 차이로는 kotlin에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다. 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용한 방법이다.
변경자 | 클래스 멤버 | 최상위 선언 |
---|---|---|
public | 모든 곳에서 볼 수 있다. | 모든 곳에서 볼 수 있다. |
internal | 같은 모듈 안에서만 볼 수 있다. | 같은 모듈 안에서만 볼 수 있다. |
protected | 하위 클래스 안에서만 볼 수 있다. | (최상위 선언에 적용 불가) |
private | 같은 클래스 안에서만 볼 수 있다. | 같은 파일 안에서만 볼 수 있다. |
예제를 하나 살펴보자. giveSpeech 함수 안의 각 줄은 가시성 규칙을 위반한다. 아래의 코드를 컴파일 하면 오류가 날 것이다.
internal open class TalkativeButton {
private fun yell() = println("Hey")
protected fun whisper() = println("Let's Talk")
}
fun TalkativeButton.giveSpeech() {
yell()
whisper()
}
잘못된 것을 찾아보자.
어떤 클래스의 Base 타입 목록에 들어있는 타입이나 제네릭 클래스의 타입 파라미터에 들어있는 타입의 가시성은 그 클래스 자신의 가시성과 같거나 더 높아야 하고, 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야 한다는 더 일반적인 규칙을 따라야 한다.
결국 위 코드의 컴파일 오류를 해결하기 위해서는 클래스 접근 위해 giveSpeech 함수의 가시성을 internal(TalkativeButton와 같은 타입)으로 바꾸거나 TalkativeButton 클래스의 가시성은 public(giveSpeech)와 같은 타입으로 바꿔야 한다.
또한 java에서는 같은 package 안에 있으면 protected 멤버에 접근할 수 있으나, kotlin에서는 그렇지 않다는 점에서 java와 kotlin의 protected는 다르다.
kotlin의 protected 가시성 규칙은 단순하다. protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 하위 클래스 안에서만 보인다. 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근할 수 없다는 사실을 꼭 기억해야 한다.
참고: kotlin의 가시성 변경자와 java
kotlin의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다. 그렇게 컴파일된 kotlin 선언의 가시성은 마치 java에서 똑같은 가시성을 사용해 선언한 결과와 같다. 유일한 예외는 private 클래스이다. 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
그러면 결국 internal 변경자는 어떻게 처리될 것인가?
java에서는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal과는 다르다. 모듈은 mudule은 보통 여러 package로 구성되며 서로 다른 module에 같은 package에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서는 public이 된다.