[Kotlin] 멤버 가시성, 내포된 클래스(inner), 지역 클래스
📕멤버 가시성
가시성은 클래스 멤버마다 다르게 지정할 수 있다. 즉, 각각 어떤 영역에서 쓰일 수 있는지 결정할 수 있다.
🔎가시성 지정이 클래스 정의 시 중요한 부분인가?
가시성을 사용해 구현과 관련한 세부사항을 캡슐화함으로써 외부 코드로부터 구현 세부사항을 격리시킬 수 있기 때문이다.
코틀린에서는 클래스 멤버의 가시성을 다음과 같은 변경자 키워드로 지정할 수 있다.
- public(공개) : 멤버를 어디서나 볼 수 있다. 디폴트 가시성이 바로 public이다. 따라서 명시적으로 pulic을 표기할 필요는 없다.
- internal(모듈 내부) : 멤버를 멤버가 속한 클래스가 포함된 컴파일 모듈 내부에서만 볼 수 있다.
- protected(보호) : 멤버를 멤버가 속한 클래스와 멤버가 속한 클래스의 모든 하위 클래스 안에서 볼 수 있다.
- private(비공개) : 멤버를 멤버가 속한 클래스 내부에서만 볼 수 있다.
⚡자바 vs 코틀린
자바에서는 클래스 멤버를 공개 멤버로 정의하고 싶으면 명시적으로 public 변경자를 붙여야 하지만
코틀린은 클래스 멤버의 디폴트 가시성이 public이라 따로 명시해줄 필요가 없다.
import java.util.*
import kotlin.*
import java.lang.*
class Person(private val firstName:String,private val familyName:String)
//파라미터로 private 선언할때는 var/val을 붙여줘야한다.
{
fun fullName() ="$firstName $familyName"
}
fun main(){
val person=Person("Lee", "Jaehan")
println(person.firstName) // error
println(person.fullName()) // not error
}
위 코드에는 firstName이 Person class에서 private 하게 선언되었기 때문에 main에서 사용할 수없지만
fullName은 public이기에 사용가능하다.
📕내포된 클래스
코틀린 클래스는 함수, 프로퍼티, 생성자 외에 다른 클래스도 멤버로 가질 수 있다.
이러한 클래스를 내포된 클래스라고 부른다.
import java.util.*
import kotlin.*
import java.lang.*
class Person(val id : Id, val age: Int){
class Id(val firstName: String, val familyName : String)
fun showMe()=println("${id.firstName} ${id.familyName},$age")
}
fun main(){
val id = Person.Id("Jaehan","Lee")
val person=Person(id,25)
person.showMe()
}
내포된 클래스를 둘러싸고 있는 클래스(Person)의 본문 밖에서는 Person.Id처럼 클래스 이름 앞에 바깥쪽 클래스의 이름을 덧붙여야만 내포된 클래스를 참조할 수 있다는 점에 유의해야 한다.
내포된 클래스에도 여러 가지 가시성을 지정할 수 있으며 바깥쪽에서 선언된 private 멤버들은 내포된 클래스에서 사용 가능하다. 하지만 내포된 클래스에서 선언된 private 멤버들은 바깥쪽 클래스에서 사용할 수 없다.
class Person(private val id :Id , private val age : Int){
class Id(private val firstName:String, private val familyName : String){
fun nameSake(person:Person)=person.id.firstName==firstName
}
fun showMe()= println("${id.firstName} ${id.familyName},$age")
}
위에서 얘기한 것처럼 바깥쪽 클래스는 자신에게 내포된 클래스의 비공개 멤버에 접근할 수 없다.
내포된 클래스에 inner를 붙이면 자신을 둘러싼 외부 클래스의 현재 인스턴스에 접근할 수 있다.
import java.util.*
import kotlin.*
import java.lang.*
class Person(val firstName : String, val familyName : String){
inner class Possession(val description: String){
fun showOwner() = println(fullName())
}
private fun fullName()="$firstName $familyName"
}
fun main(){
val person=Person("Jaehan","Lee")
val wallet=person.Possession("Wallet")
wallet.showOwner()
}
여기서 내부(inner) 클래스 생성자를 호출할 때 person.Possession("Wallet")처럼 외부 클래스 인스턴스를 지정해야 한다는 점에 유의해야 한다.
다른 멤버들과 마찬가지로 내부 클래스를 가리킬 때도 this를 생략할 수 있다.
일반적으로 this는 항상 내부의 클래스 인스턴스를 가리킨다. 따라서 내부 클래스 본문에서 this는 내부 클래스 자신을 가리킨다. 만약 내부 클래스 본문에서 외부 클래스 인스턴스를 가리켜야 하는 상황이 온다면 한정시킨 this 식을 사용해야 한다.
[@기호 다음에 오는 식별자는 가리키고 싶은 외부 클래스 이름이다.]
class Person(val firstName : String, val familyName: String){
inner class INclass(val description : String){
fun getOwner()=this@Person
}
}
📕지역 클래스
자바처럼 코틀린에서도 함수 본문에서 클래스를 정의할 수 있다.
이런 지역 클래스는 자신을 둘러싼 코드 블록 안에서만 쓰일 수 있다.
fun main(){
class Point(val x : Int , val y : Int){
fun shift(dx : Int, dy : Int ): Point=Point(x+dx, y+dy)
override fun toString()="($x, $y)"
}
val p= Point(10,10)
println(p.shift(-1,3))
}
fun foo(){
println(Point(0,0)) //Error
}
main() 안에 클래스가 정의되었기 때문에 main 외부에서는 사용하지 못함.
지역 클래스는 지역 함수와 비슷하게 자신을 둘러싼 코드의 선언에 접근할 수 있다.(읽기, 변경)
내포된 클래스와 달리 지역 클래스에는 가시성 변경자를 붙일 수 없다.
지역 클래스의 디폴트 영역은 자신을 둘러싼 블록으로 제한된다.
✔정리
- 가시성은 멤버마다 다르게 지정할 수 있으며, 4가지로 지정할 수 있다.
- public, internal, protected, private,
- 각각의 쓰임에 따라서 멤버가 쓰일 수 있는 영역을 결정한다.
- 코틀린 클래스는 함수, 프로퍼티, 생성자 이외에도 클래스를 멤버로 가질 수 있다. 이러한 클래스를 내포된 클래스라고 한다.
- 내포된 클래스에서 외부 클래스의 비공개(private) 멤버에 접근할 수 없지만 inner를 붙인다면 접근 가능하다.
- 내부에서 외부 클래스의 인스턴스를 가리키려고 한다면 this@외부 클래스 이름 형식으로 하면 접근 가능하다.
- 함수 본문에서도 클래스를 정의할 수 있는데 이러한 클래스를 지역 클래스라고 한다.
- 자신을 둘러싼 블록 안에서만 사용 가능하다.
- 자신을 둘러싼 코드의 선언에 접근할 수 있다.(변수를 읽고, 변수를 변경하는 것)
- 가시성 변경자를 붙일 수 없다.