-
접근제한자, 접근제어자 (public, private, protected, internal) 이 뭔데? - KotlinKotlin 2020. 6. 4. 17:16
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 가능 } }