접근제한자, 접근제어자 (public, private, protected, internal) 이 뭔데? - Kotlin
tl;dr
public > protected | internal > private
private : 속해있는 class 에서만 접근가능
internal : private + 같은 모듈 안에서 접근가능
protected : private + 상속받은 클래스에서도 접근가능
public : protected + 접근제한 없음
서론
kotlin 을 처음 시작하기 전에 kotlin 의 접근제한자는 어떻게 구성되어있는지 알아보고 Java 의 접근제한자와는 어떤 차이점이 있는지 알아보면서 배웠던 것들을 공유하기 위해 포스팅을 하려고 한다.
Java의 접근제한자와의 비교에 중점을 두려고 하니 이전 포스팅을 먼저 확인하고 오면 좋을 것 같다.
본론
kotlin 의 접근제한자의 종류로는 public, private, protected 그리고 internal 이 있다.
사용하는 변수나 메소드, 생성자 앞에 붙여서 해당 변수, 메소드, 생성자의 사용할 수 있는 자격을 어디까지 줄 것인지 정해 준다.
예를 들어서 설명해 보면
살고 있는 집에 도어락이 지문인식으로 되어있다고 하면, 지문등록을 내 것만 할 것인지,
가족들도 지문등록을 할 것인지 아니면 친구들이나 누구라도 들어올 수 있도록 만들어 줄 건지 하는 것이다.
이러한 접근제한자는 객체지향 ( Object-oriented Programing ) 언어 의 3대요소 캡슐화, 상속 그리고 다향성 중에서 캡슐화에 속한다.
접근제한자를 간단하게 표로 표현해 보면 다음과 같다.
java 와 kotlin 의 접근제한자를 비교해 보면 종류의 가지수는 같지만, default 가 없어지고 internal 이 새로 생겼다는 것을 알 수 있다.
kotlin 에서의 접근제한자를 간단하게 표로 표현해 보면 다음과 같다.
제한자 \ 범위 | 같은 class | 같은 module | 다른 module | 제한없음 |
private | O | |||
protected | O | (상속 받았을 때 O) | (상속 받았을 때 O) | |
internal | O | O | ||
public | O | O | O | O |
표에 나타낸 것과 같이 private 에서 default, protected, public 으로 갈수록 점점 제한이 풀린다는 것을 알 수 있다.
이는 java 에서와 같은 내용이지만 internal 부분을 확인해 보면 같은 package 에서 같은 module 로 바뀌었다는 것을 알 수 있다.
각 제한자가 어떤 역할을 하는 알아보기 이전에 기본용어 먼저 짚고 넘어가도록 하자.
project - 최상위 개념이다. 여러 module을 가질 수 있다.
module - 여러 package를 가질 수 있다.
package - 여러 member, function, class 를 가질 수 있다.
class - member, function을 가질 수 있다.
간단하게 project > module > package > class 라고 생각하면 된다.
예제를 보면서 어떤 얘기인지 확인해 보도록 하자.
1. private
open class OuterK {
private val a = 1
private class Inner {
val e = 5
}
}
class SubClass : OuterK() {
init {
val a = this.a // access 불가
val e = Inner().e // access 불가
}
}
class Unrelated(o: OuterK) {
init {
val a = o.a // access 불가
val e = OuterK.Inner().e // access 불가
}
}
OuterK 클래스에 private 가 붙은 변수나 클래스 모두 같은 패키지라도 접근할 수 없다는 것을 알 수 있다.
상속을 받더라도 접근할 수 없다.
이것을 통해 private 이 붙게 되면 속해있는 class 내부를 제외하고는 절대 접근할 수 없다는 것을 알 수 있다.
즉 완전 폐쇄적인 상황에서만 사용해야 한다.
2. protected
open class OuterK {
protected open val b = 2
protected class Inner2 {
val f = 6
}
}
class SubClass : OuterK() {
override val b = 5 // access 가능
init {
val b = this.b // access 가능
val f = Inner2().f // access 가능
}
}
class Unrelated(o: OuterK) {
init {
val b = o.b // access 불가
val f = OuterK.Inner2().f // access 불가
}
}
OuterK 클래스에 protected 가 붙어있는 변수나 클래스는 위의 예시에서 확인할 수 있듯이 동일한 class 내에서 뿐만아니라 상속받은 class 에서도 접근이 가능하지만 다른 class 에서는 접근할 수 없는 것을 확인할 수 있다.
표를 보면 상속받은 class 에서는 접근이 가능하다고 했는데
그렇다면 다른 패키지에서 호출할 때는 어떻게 되는지 확인해보자.
class SubClassK : OuterK() {
init {
val b = b // access 가능
val f = Inner2().f // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val b = o.b // access 불가
val f = OuterK.Inner2().f // access 불가
}
}
처음 예시에서와 완전히 똑같다는 것을 알 수 있다. 이를통해 다른 패키지에서도 접근할 수 있다는 것을 알 수 있다.
그렇다면 모듈이 바뀐다면 어떻게 될까?
class SubClassK : OuterK() {
init {
val b = b // access 가능
val f = Inner2().f // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val b = o.b // access 불가
val f = OuterK.Inner2().f // access 불가
}
}
모듈이 바뀌더라도 처음과 똑같이 상속받을 때만 접근할 수 있는 것을 알 수 있다.
이를 통해 protected 가 붙게 되면 상속하는 class 에서는 접근할 수 있지만, 그외에는 접근할 수 없다는 것을 알 수 있다.
즉 다른 class 에서 상속이 필요한 상황에 사용해야 한다.
3. internal
open class OuterK {
internal val c = 3
internal class Inner3 {
val g = 7
}
}
class SubClass : OuterK() {
init {
val c = this.c // access 가능
val g = Inner3().g // access 가능
}
}
class Unrelated(o: OuterK) {
init {
val c = o.c // access 가능
val g = OuterK.Inner3().g // access 가능
}
}
OuterK 클래스에 internal 가 붙어있는 변수나 클래스는 위의 예시에서 확인할 수 있듯이 동일한 class 나 다른 class 내에서 뿐만아니라 상속받은 class 에서도 접근이 가능하다는 것을 확인할 수 있다.
표를 보면 같은 모듈 에서만 상속된 class 가 접근이 가능하다고 했는데
그렇다면 다른 패키지랑 다른 모듈에서 호출할 때는 어떻게 되는지 확인해보자.
같은 모듈이고 다른 패키지 일 때
class SubClassK : OuterK() {
init {
val c = c // access 가능
val g = Inner3().g // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val c = o.c // access 가능
val g = OuterK.Inner3().g // access 가능
}
}
다른 모듈일 때
class SubClassK : OuterK() {
init {
val c = c // access 불가
val g = Inner3().g // access 불가
}
}
class UnrelatedK(o: OuterK) {
init {
val c = o.c // access 불가
val g = OuterK.Inner3().g // access 불가
}
}
위의 예시들을 통해 같은 모듈에서 패키지가 바뀌게 되면 영향이 없지만 모듈이 바뀌게 되면 접근이 제한된다는 것을 알 수 있다.
이를 통해 internal 가 붙게 되면 상속과 상관 없이 모듈이 같다면 접근할 수 있고 그렇지 않다면 접근할 수 없다는 것을 알 수 있다.
즉 같은 모듈내 에서만 적용하고 싶은 상황에 사용해야 한다.
4. public
같은 모듈 같은 패키지 일 때 (java 에서의 default 접근제한자 표현법이랑 같음. public 을 붙여줘도 됨.)
open class OuterK {
val d = 4 // public val d = 4 이랑 같음
class Inner4 {
val h = 8
}
}
class SubClass : OuterK() {
init {
val d = this.d // access 가능
val h = Inner4().h // access 가능
}
}
class Unrelated(o: OuterK) {
init {
val d = o.d // access 가능
val h = OuterK.Inner4().h // access 가능
}
}
같은 모듈 다른 패키지 일 때
class SubClassK : OuterK() {
init {
val d = d // access 가능
val h = Inner4().h // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val d = o.d // access 가능
val h = OuterK.Inner4().h // access 가능
}
}
다른 모듈 일 때
class SubClassK : OuterK() {
init {
val d = d // access 가능
val h = Inner4().h // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val d = o.d // access 가능
val h = OuterK.Inner4().h // access 가능
}
}
public 의 경우에는 접근제한없이 어떤 상황이라도 접근할 수 있다.
위의 예시와 같이 같은 패키지 뿐만 아니라, 같은 모듈 그리고 다른 모듈에서도 접근이 가능하다.
이를 통해 모든 상황에 대해 public 은 접근이 가능하다는 것을 알 수 있다.
즉 상황에 구애받지 않고 사용하고 싶을 때 public 을 붙여주면 된다.
마지막으로 kotlin의 접근제한자 사용 범위에 대해서 알아보도록 하자.
클래스 | public, internal, private |
생성자 | public, internal, protected, private |
멤버변수 | public, internal, protected, private |
멤버메소드 | public, internal, protected, private |
지역번수 | 접근제한자 사용 제한 (사용할 수 없음) |
결론
kotlin의 접근제한자에 대해 알아보았다.
비교적 어렵지 않은 내용이지만, java 와의 차이는 뚜렷이 있다는 것을 확인할 수 있었다.
- java 에서 아무런 접근제한자도 적지 않으면 default 였던 데에 반해 kotlin 에서는 public 이 된다
- 같은 모듈일 때 다른 모듈에서의 접근을 제한하려면 internal 을 상속받을 때만 protected 를 사용한다
- java 에서와 달리 kotlin 에서의 protected 는 상속받았을 때만 접근할 수 있다
정확한 사용법을 알아보지 않고 사용하게 되면 코드가 중구난방식이 될 수 있기 때문에 주의 해야할 필요가 있다. 그리고 kotlin 의 기본이 되는 내용이니 만큼 반드시 머리속에 들어가 있어야하는 부분이다.
참조
https://kotlinlang.org/docs/reference/visibility-modifiers.html
전체코드
// 같은 모듈 같은 패키지 일 때
open class OuterK {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // public val d = 4 이랑 같음
private class Inner {
val e = 5
}
protected class Inner2 {
val f = 6
}
internal class Inner3 {
val g = 7
}
class Inner4 {
val h = 8
}
}
class SubClass : OuterK() {
override val b = 2 // access 가능
init {
val a = this.a // access 불가
val b = this.b // access 가능
val c = this.c // access 가능
val d = this.d // access 가능
val e = Inner().e // access 불가
val f = Inner2().f // access 가능
val g = Inner3().g // access 가능
val h = Inner4().h // access 가능
}
}
class Unrelated(o: OuterK) {
init {
val a = o.a // access 불가
val b = o.b // access 불가
val c = o.c // access 가능
val d = o.d // access 가능
val e = OuterK.Inner().e // access 불가
val f = OuterK.Inner2().f // access 불가
val g = OuterK.Inner3().g // access 가능
val h = OuterK.Inner4().h // access 가능
}
}
// 같은 모듈 다른 패키지 일 때
class SubClassK : OuterK() {
init {
val a = a // access 불가
val b = b // access 가능
val c = c // access 가능
val d = d // access 가능
val e = Inner().e // access 불가
val f = Inner2().f // access 가능
val g = Inner3().g // access 가능
val h = Inner4().h // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val a = o.a // access 불가
val b = o.b // access 불가
val c = o.c // access 가능
val d = o.d // access 가능
val e = OuterK.Inner().e // access 불가
val f = OuterK.Inner2().f // access 불가
val g = OuterK.Inner3().g // access 가능
val h = OuterK.Inner4().h // access 가능
}
}
// 다른 모듈 일 때
class SubClassK : OuterK() {
init {
val a = a // access 불가
val b = b // access 가능
val c = c // access 불가
val d = d // access 가능
val e = Inner().e // access 불가
val f = Inner2().f // access 가능
val g = Inner3().g // access 불가
val h = Inner4().h // access 가능
}
}
class UnrelatedK(o: OuterK) {
init {
val a = o.a // access 불가
val b = o.b // access 불가
val c = o.c // access 불가
val d = o.d // access 가능
val e = OuterK.Inner().e // access 불가
val f = OuterK.Inner2().f // access 불가
val g = OuterK.Inner3().g // access 불가
val h = OuterK.Inner4().h // access 가능
}
}