KB 15 가시성 — package-private 이 없다
고친 사람 github-actions[bot]
0. 수식어를 빼면 자바는 숨기고 코틀린은 연다
자바에서 클래스 앞의 public 을 빼면 그 클래스는 같은 패키지 안에서만 보입니다. 이 기본 수준을 package-private 이라고 부릅니다. 패키지 밖에 내놓기 싫은 도우미 클래스를 숨길 때 흔히 쓰는 방법입니다.
shop 패키지에 수식어 없는 Counter 를 두고, app 패키지에서 불러 봤습니다.
// shop/Counter.java
package shop;
class Counter {
int count = 0;
}
// app/Main.java
package app;
import shop.Counter;
public class Main {
public static void main(String[] args) {
System.out.println(new Counter().count);
}
}
두 파일을 javac 에 함께 넘겼습니다. 결과는 예상대로 거절입니다.
$ javac -d out shop/Counter.java app/Main.java
app/Main.java:3: error: Counter is not public in shop; cannot be accessed from outside package
import shop.Counter;
^
app/Main.java:7: error: Counter is not public in shop; cannot be accessed from outside package
System.out.println(new Counter().count);
^
2 errors
import 줄과 생성자 호출 줄, 두 곳에서 막혔습니다.
이제 같은 두 파일을 코틀린으로 옮깁니다. 수식어는 역시 하나도 붙이지 않았습니다.
// shop/Counter.kt
package shop
class Counter {
var count = 0
}
// app/Main.kt
package app
import shop.Counter
fun main() {
println(Counter().count) // 0
}
var count = 0— 클래스 안에 쓴var는 프로퍼티(property)입니다. 필드와 게터·세터를 한 이름으로 묶은 선언입니다.var는 다시 대입할 수 있다는 뜻입니다Counter()— 생성자 호출입니다. 코틀린에는new가 없습니다fun main()— 클래스 밖, 파일 바로 아래에 쓴 함수입니다. 이런 선언을 톱레벨(top-level) 선언이라고 부릅니다
코틀린 컴파일러 kotlinc 로 컴파일하고, java 처럼 클래스를 실행하되 코틀린 표준 라이브러리를 알아서 챙기는 kotlin 명령으로 돌렸습니다. -d out 은 결과물을 out 폴더에 쓰라는 옵션이고, app.MainKt 는 Main.kt 의 톱레벨 함수를 담으려고 컴파일러가 지은 클래스입니다. 이름은 파일 이름 뒤에 Kt 를 붙인 것입니다.
$ kotlinc shop/Counter.kt app/Main.kt -d out
$ kotlin -cp out app.MainKt
에러 없이 0 이 찍혔습니다. 자바에서 두 번 막혔던 그 호출을, 코틀린은 다른 패키지에서 그대로 해냈습니다. 코틀린에서 수식어를 안 쓰면 public 이 기본이기 때문입니다.
그러면 패키지 밖에 숨기고 싶은 클래스는 어떻게 숨길까요. 코틀린에는 package-private 을 뜻하는 키워드가 따로 없습니다. 대신 모듈(함께 컴파일되는 코틀린 파일 묶음)과 파일을 경계로 씁니다. 이 편은 그 경계들을 하나씩 자바와 맞대 봅니다. 출력은 전부 Kotlin 2.4.10, JDK 21 에서 직접 돌린 결과입니다.
1. 네 가지 가시성 — public, internal, protected, private
가시성(visibility)은 어떤 선언을 어디에서 부를 수 있는지 정하는 규칙입니다. 자바에서는 접근 제어자(access modifier) public·protected·private 이 이 일을 합니다. 코틀린에서는 선언 앞에 붙는 이 낱말들을 가시성 수식어(visibility modifier)라고 부릅니다.
| 수식어 | 클래스 멤버에 붙이면 | 톱레벨 선언에 붙이면 | 자바에서는 |
|---|---|---|---|
| 없음 | 어디서나(public) |
어디서나(public) |
같은 패키지 안 |
internal |
같은 모듈 안 | 같은 모듈 안 | 없는 수식어 |
protected |
그 클래스와 자식 클래스 안 | 붙일 수 없다 | 자식 클래스 + 같은 패키지 |
private |
그 클래스 안 | 그 파일 안 | 그 클래스 안 |
코틀린 칸 네 줄 가운데 패키지를 경계로 쓰는 수식어는 하나도 없습니다(코틀린 문서).
표를 확인하려고 box 패키지에 수식어만 다른 프로퍼티 넷을 뒀습니다. 톱레벨 함수에는 protected 를 붙여 봤습니다.
// Box.kt
package box
open class Box {
val a = 1
internal val b = 2
protected val c = 3
private val d = 4
}
protected fun top() {} // 컴파일 오류
// Main.kt
package box
fun main() {
val box = Box()
println(box.a)
println(box.b)
println(box.c) // 컴파일 오류
println(box.d) // 컴파일 오류
}
open class— 상속을 허용한 클래스입니다.protected는 자식 클래스가 있어야 뜻이 있어서 붙였습니다val— 다시 대입할 수 없는 프로퍼티입니다
Main.kt 는 같은 패키지에서 넷을 차례로 읽습니다. 두 파일을 kotlinc 한 번에 넘겼습니다.
$ kotlinc Box.kt Main.kt -d out
Box.kt:10:1: error: modifier 'protected' is not applicable to 'top level function'.
protected fun top() {}
^^^^^^^^^
Main.kt:7:17: error: cannot access 'val c: Int': it is protected in 'box.Box'.
println(box.c)
^
Main.kt:8:17: error: cannot access 'val d: Int': it is private in 'box.Box'.
println(box.d)
^
에러는 셋입니다. 첫 에러는 Box.kt 쪽으로, 톱레벨 함수에는 protected 를 붙이는 것부터 안 된다는 뜻입니다(not applicable to 'top level function'). 나머지 둘은 Main.kt 가 프로퍼티를 읽는 줄입니다.
| 프로퍼티 | 결과 | 이유 |
|---|---|---|
a |
통과 | 수식어가 없어 public 이다 |
b |
통과 | internal 인데 두 파일을 한 번에 컴파일해 같은 모듈이 됐다 |
c |
막힘 | protected 다 |
d |
막힘 | private 이라 클래스 밖에서는 못 읽는다 |
c 가 같은 패키지인데도 막힌 이유는 4절에서 봅니다.
2. internal — 모듈 밖 코틀린에서는 막히고, 자바에서는 바뀐 이름으로 불린다
internal 이 모듈 밖에서 정말 막히는지, 그리고 자바 쪽에서는 무엇으로 보이는지 봅니다.
모듈의 경계부터 정합니다. 명령줄에서는 kotlinc 한 번에 넘긴 파일들이 한 모듈이 됩니다. 이 편의 실험은 이 규칙 하나만 씁니다.
빌드 도구를 쓰면 IntelliJ IDEA 모듈, Maven 프로젝트, Gradle 소스 세트 하나가 각각 모듈 하나입니다. 소스 세트는 main·test 처럼 나눠 컴파일하는 소스 묶음입니다.
lib 패키지에 internal 멤버 함수와 internal 톱레벨 함수를 하나씩 뒀습니다. app 패키지의 Main.kt 가 그 둘 가운데 멤버 쪽을 부릅니다.
// lib/Greeter.kt
package lib
class Greeter {
internal fun hello(): String {
return "hello"
}
fun greet(): String {
return hello() + "!"
}
}
internal fun topHello(): String {
return "top"
}
// app/Main.kt
package app
import lib.Greeter
fun main() {
val g = Greeter()
println(g.greet())
println(g.hello()) // 컴파일 오류
}
internal fun hello(): String— 같은 모듈 안에서만 부를 수 있는 멤버 함수입니다. 괄호 뒤: String은 반환 타입입니다val g = Greeter()— 타입을 안 적으면 값에서Greeter타입을 알아냅니다
Greeter.kt 를 lib 모듈로 먼저 컴파일했습니다. 그 결과물을 classpath 로 받아 Main.kt 를 app 모듈로 따로 컴파일했습니다.
$ kotlinc lib/Greeter.kt -module-name lib -d libout
$ kotlinc app/Main.kt -cp libout -module-name app -d appout
app/Main.kt:8:15: error: cannot access 'fun hello(): String': it is internal in 'lib.Greeter'.
println(g.hello())
^^^^^
-module-name lib— 모듈 이름을lib로 정하는 옵션입니다. 결과 폴더의META-INF에lib.kotlin_module파일이 생깁니다-cp libout— 이미 컴파일된lib모듈의 클래스를 classpath 로 넘깁니다
같은 두 파일을 kotlinc lib/Greeter.kt app/Main.kt -d out 한 번으로 컴파일하면 에러가 나지 않습니다. 실행하면 hello! 와 hello 가 차례로 찍힙니다. 패키지가 달라도 모듈이 같으면 열린다는 뜻입니다.
자바에서는 hello 가 아니라 hello$lib 다
컴파일 결과인 .class 파일, 곧 클래스 파일은 멤버마다 접근 범위를 접근 플래그(access flag)로 적습니다. 플래그로 적을 수 있는 것은 자바와 같은 넷(public·protected·private·수식어 없음)뿐이라, internal 을 적을 플래그가 없습니다. 그래서 JDK 에 딸린 javap 로 lib 쪽 결과물의 선언 목록을 열어 봤습니다. -p 는 private 멤버까지 보여 달라는 옵션입니다.
$ javap -p libout/lib/Greeter.class
Compiled from "Greeter.kt"
public final class lib.Greeter {
public lib.Greeter();
public final java.lang.String hello$lib();
public final java.lang.String greet();
}
$ javap -p libout/lib/GreeterKt.class
Compiled from "Greeter.kt"
public final class lib.GreeterKt {
public static final java.lang.String topHello();
}
internal fun hello() 가 public 메서드 hello$lib() 가 됐습니다. 이름 뒤의 $lib 는 모듈 이름입니다. 컴파일러가 선언 이름에 표시를 덧붙여 바꾸는 이런 일을 이름 맹글링(name mangling)이라고 부릅니다.
반면 톱레벨 함수 topHello() 는 이름이 그대로입니다. 이름이 바뀌는 것은 클래스 안의 internal 함수와 internal 프로퍼티의 게터·세터입니다. 톱레벨 선언과 생성자는 이름이 바뀌지 않습니다.
이름을 바꾸는 이유는 둘입니다(코틀린 문서). 첫째, 자바 클래스가 코틀린 클래스를 상속할 때 다른 모듈의 internal 멤버를 모르고 재정의하는 사고를 막습니다.
둘째, 코틀린에서 서로 안 보이는 두 멤버가 JVM 에서 부딪치지 않습니다. 모듈 둘에 같은 이름의 함수를 두고 재 봤습니다.
// lib 모듈
package lib
open class P {
internal fun f() {}
}
// app 모듈
package app
import lib.P
class C : P() {
fun f() {}
}
P.f() 와 C.f() 는 코틀린에서 서로 안 보이는 별개 함수입니다. 컴파일한 둘을 열면 부모 쪽은 public final void f$lib();, 자식 쪽은 public final void f(); 입니다. 이름이 달라져 JVM 에서도 두 메서드가 따로 남습니다.
그러면 자바는 이 public 메서드를 부를 수 있을까요. $ 는 자바 식별자에 쓸 수 있는 문자라, hello$lib 는 자바 코드에 그냥 적을 수 있습니다. 바뀐 이름으로 두 함수를 부르는 자바 클래스를 써서, lib 모듈 결과물을 classpath 에 넣고 컴파일해 돌렸습니다. 자바 결과물은 코틀린 것과 섞이지 않게 jout 폴더에 두었습니다.
import lib.Greeter;
public class UseLib {
public static void main(String[] args) {
Greeter g = new Greeter();
System.out.println(g.hello$lib());
System.out.println(lib.GreeterKt.topHello());
}
}
$ javac -cp libout -d jout UseLib.java
$ java -cp jout:libout UseLib
hello
top
javac 는 경고 하나 없이 통과시켰고, 실행하면 두 값이 찍힙니다. hello$lib() 는 코틀린에서 모듈 밖이라 막혔던 호출입니다.
그러면 같은 클래스 파일을 받은 kotlinc 는 무엇을 보고 막았을까요. 코틀린 컴파일러는 클래스 파일에 @Metadata 애노테이션을 붙입니다. 원래 이름과 가시성 같은 코틀린 전용 정보를 그 안에 적습니다. 앞의 app 모듈 컴파일은 이 애노테이션에서 hello 가 internal 이라는 것을 읽고 막았습니다. javac 는 이것을 읽지 않아 접근 플래그의 public 만 봅니다.
internal 은 코틀린 컴파일러가 지키는 규칙입니다. 접근 플래그로는 public 이라서 자바 코드는 바뀐 이름만 알면 부를 수 있습니다. 참고로 코틀린의 모듈은 자바 9 의 모듈 시스템(module-info.java)과는 다른 개념입니다.
3. 톱레벨 private 은 파일 단위다
자바에서 package-private 으로 숨기던 도우미 메서드를, 코틀린에서는 톱레벨 함수에 private 을 붙여 숨깁니다. 같은 패키지에 파일 두 개를 두고 한쪽의 private 함수를 양쪽에서 불렀습니다.
// A.kt
package geo
private fun secret(): Int {
return 42
}
private class Memo
fun reveal(): Int {
return secret()
}
// B.kt
package geo
fun peek(): Int {
return secret()
}
private fun secret()— 이 파일 안에서만 부를 수 있는 톱레벨 함수입니다private class Memo— 본문이 없는 클래스에private을 붙였습니다. 뒤에서 클래스 파일을 열 때 씁니다
A.kt 의 reveal() 과 B.kt 의 peek() 이 똑같이 secret() 을 부릅니다. 둘은 같은 패키지이고, kotlinc 한 번에 넘겨 같은 모듈이기도 합니다.
$ kotlinc A.kt B.kt -d out
B.kt:4:12: error: cannot access 'fun secret(): Int': it is private in file.
return secret()
^^^^^^
B.kt 만 막혔습니다. 에러 문구도 private in file, 곧 「파일 안에서만 private」입니다. 같은 파일의 reveal() 은 통과했습니다. 자바에는 톱레벨 함수가 없어서, 가장 가까운 모양은 클래스의 private static 메서드입니다. 같은 패키지에 클래스 A 와 B 를 두고, B 에서 A 의 private static 메서드를 불렀습니다.
// A.java
package geo;
public class A {
private static int secret() { return 42; }
}
// B.java
package geo;
public class B {
public static int peek() {
return A.secret();
}
}
$ javac -d jout A.java B.java
B.java:5: error: secret() has private access in A
return A.secret();
^
1 error
같은 패키지여도 클래스가 다르면 막힙니다. 자바의 private 은 클래스 단위, 코틀린의 톱레벨 private 은 파일 단위입니다.
함수는 파일 밖에서 막히고, 클래스는 자바에 열린다
JVM 에는 파일이라는 단위가 없습니다. 그러면 파일 단위 private 은 클래스 파일에서 무엇이 될까요. 에러 나는 B.kt 를 빼고 A.kt 만 컴파일해 열었습니다.
$ javap -p out/geo/AKt.class out/geo/Memo.class
Compiled from "A.kt"
public final class geo.AKt {
private static final int secret();
public static final int reveal();
}
Compiled from "A.kt"
final class geo.Memo {
public geo.Memo();
}
함수 쪽은 간단합니다. A.kt 의 톱레벨 함수는 전부 AKt 클래스 하나에 들어갑니다. 그러니 AKt 안의 private static 이면 곧 「이 파일 안에서만」이 됩니다.
클래스 쪽이 반전입니다. Memo 는 AKt 에 들어가지 않고 제 이름의 클래스 파일이 따로 생겼습니다. 그 선언이 final class geo.Memo 로, public 도 private 도 없습니다. 톱레벨 클래스는 JVM 에서 private 이 될 수 없어서 수식어 없음, 곧 package-private 으로 남은 것입니다.
그렇다면 같은 패키지의 자바 코드는 이 Memo 를 만들 수 있어야 합니다. 확인하려고 자바 클래스 UseMemo 를 같은 패키지에 두고 돌렸습니다.
package geo;
public class UseMemo {
public static void main(String[] args) {
System.out.println(new Memo().getClass());
}
}
$ javac -cp out -d out UseMemo.java
$ java -cp out geo.UseMemo
class geo.Memo
자바 쪽은 막히지 않았습니다. 이 편의 코틀린 결과물에서 package-private 이 나온 곳은 코틀린의 private 을 번역한 이 한 군데뿐입니다.
톱레벨 private 은 코틀린 코드에게는 파일 단위입니다. 함수는 파일이름Kt 의 private static 으로 막히지만, private 클래스는 같은 패키지의 자바 코드에게 열려 있습니다.
4. protected — 같은 패키지에서도 안 보인다
자바의 protected 는 두 곳에 열립니다. 자식 클래스, 그리고 같은 패키지의 모든 클래스입니다. zoo 패키지에 Animal 과, 자식이 아닌 Keeper 를 두고 자바부터 돌렸습니다.
// Animal.java
package zoo;
public class Animal {
protected String sound() {
return "grr";
}
}
// Keeper.java
package zoo;
public class Keeper {
public static void main(String[] args) {
System.out.println(new Animal().sound());
}
}
$ javac -d jout Animal.java Keeper.java
$ java -cp jout zoo.Keeper
grr
자바는 통과입니다. 같은 패키지라는 이유만으로 protected 메서드를 불렀습니다. 코틀린으로 옮기면서 진짜 자식 클래스 Dog 도 하나 더했습니다.
// Animal.kt
package zoo
open class Animal {
protected fun sound(): String {
return "grr"
}
}
class Dog : Animal() {
fun bark(): String {
return sound()
}
}
// Keeper.kt
package zoo
fun main() {
println(Dog().bark())
println(Animal().sound()) // 컴파일 오류
}
open class Animal— 코틀린 클래스는 기본으로 상속을 막습니다.open을 붙여야 자식 클래스를 만들 수 있습니다class Dog : Animal()— 콜론 뒤가 부모 클래스입니다.Animal()의 괄호는 부모 생성자 호출입니다. 자바의extends에 해당합니다
Keeper.kt 의 Dog().bark() 는 자식을 거쳐 부릅니다. Animal().sound() 는 자바처럼 바로 부릅니다.
$ kotlinc Animal.kt Keeper.kt -d out
Keeper.kt:5:22: error: cannot access 'fun sound(): String': it is protected in 'zoo.Animal'.
println(Animal().sound())
^^^^^
막힌 것은 Animal().sound() 뿐입니다. 코틀린의 protected 는 자식 클래스에만 열리고, 같은 패키지는 따지지 않습니다.
같은 패키지의 자바 코드는 그대로 부른다
internal 처럼 이 규칙도 코틀린 컴파일러만 지키는 것일까요. Animal.kt 만 컴파일하고, 앞의 Keeper.java 와 본문이 같은 자바 클래스 JKeeper 를 같은 패키지에 두고 불렀습니다.
$ javac -cp out -d out JKeeper.java
$ java -cp out zoo.JKeeper
grr
코틀린이 막았던 그 호출을, 같은 패키지의 자바 코드 JKeeper 는 해냈습니다. 클래스 파일에 적힌 protected 가 자바의 뜻, 곧 같은 패키지까지 열린 protected 이기 때문입니다. 자바와 코틀린의 protected 는 이 점에서 갈립니다(코틀린 문서).
코틀린의 protected 는 자바보다 좁게 자식 클래스에만 열립니다. 다만 그 좁힘은 코틀린 코드끼리의 이야기입니다. 같은 패키지의 자바 코드에는 자바의 protected 로 보입니다.
5. 생성자와 세터의 가시성 — private set
자바에서 「밖에서는 읽기만, 쓰기는 클래스 안에서만」을 만들려면 private 필드에 public 게터를 달고 세터는 안 만듭니다. 생성자를 막을 때는 private 생성자를 씁니다. 코틀린은 가시성 수식어를 세터에만, 또는 생성자에만 따로 붙입니다. bank 패키지의 Account 에는 세터에, Token 에는 생성자에 붙였습니다.
// Account.kt
package bank
class Account {
var balance: Int = 0
private set
fun deposit(amount: Int) {
balance += amount
}
}
class Token private constructor()
// Main.kt
package bank
fun main() {
val acc = Account()
acc.deposit(100)
println(acc.balance)
acc.balance = 0 // 컴파일 오류
Token() // 컴파일 오류
}
var balance: Int = 0— 타입을 적은 프로퍼티입니다. 이름 뒤 콜론 다음이 타입입니다private set— 프로퍼티 바로 아래 줄에 적어 세터에만private을 붙입니다. 게터는public으로 남습니다class Token private constructor()— 클래스 이름 뒤의 괄호가 생성자(주 생성자)입니다. 여기에 수식어를 붙이려면constructor키워드를 함께 적습니다(코틀린 문서)
Main.kt 는 println(acc.balance) 로 읽고, acc.balance = 0 으로 쓰고, Token() 으로 생성자를 부릅니다.
$ kotlinc Account.kt Main.kt -d out
Main.kt:7:9: error: cannot access 'balance': it is private in 'bank.Account'.
acc.balance = 0
^^^^^^^
Main.kt:8:5: error: cannot access 'constructor(): Token': it is private in 'bank.Token'.
Token()
^^^^^
읽기는 통과했습니다. 막힌 것은 쓰기와 생성자 호출입니다. Account.kt 에는 에러가 없으니, 클래스 안의 deposit 에서는 balance 에 쓸 수 있습니다.
자바에서 부르려 해도 세터가 없다
그러면 클래스 파일에는 private 세터 메서드가 들어 있을까요. Account.kt 의 두 클래스를 열었습니다.
$ javap -p out/bank/Account.class out/bank/Token.class
Compiled from "Account.kt"
public final class bank.Account {
private int balance;
public bank.Account();
public final int getBalance();
public final void deposit(int);
}
Compiled from "Account.kt"
public final class bank.Token {
private bank.Token();
}
getBalance() 는 있는데 setBalance 는 private 으로도 없습니다. 세터 본문을 따로 쓰지 않은 기본 세터라서 컴파일러가 세터 메서드를 만들지 않았습니다. 세터 본문을 직접 쓰면 private 세터 메서드가 생깁니다. Token 의 생성자는 private bank.Token() 으로, 자바의 private 생성자와 같은 모양입니다.
자바로 손수 쓴 클래스와 모양이 같습니다. 필드는 private, 게터는 public, 세터는 없는 Account.java 를 컴파일해 javap -p 로 열자 멤버 목록이 위와 한 줄씩 맞았습니다. 다른 것은 코틀린 메서드에 붙은 final 뿐입니다.
기본 세터에 붙인 private set 은 자바에서 세터를 안 만든 클래스와 같은 클래스 파일이 됩니다. private constructor() 는 자바의 private 생성자로 남습니다.
6. 한 장 요약
코틀린에는 package-private 이 없다. 수식어를 빼면
public이다. 숨기는 경계는 패키지 대신 모듈(internal)과 파일(톱레벨private)이다.protected는 자식 클래스에만 열린다. 다만 이 경계들은 코틀린 컴파일러가 지키는 것이다. 자바 코드는 클래스 파일에 적힌 JVM 수식어 넷만 보고, 그 넷의 규칙으로 부른다.
| 하고 싶은 일 | 자바 | 코틀린 | 클래스 파일에 남는 모양 |
|---|---|---|---|
| 수식어 없이 선언 | package-private | public |
public |
| 같은 패키지에만 열기 | 수식어 없음 | 수단이 없다 | — |
| 같은 모듈에만 열기 | 수단이 없다 | internal |
public. 클래스 안의 함수와 프로퍼티 게터·세터는 이름 뒤에 $모듈이름 |
| 같은 파일에만 열기 | 수단이 없다 | 톱레벨 private |
함수는 파일이름Kt 의 private static(같은 파일의 클래스가 부르면 access$ 접근용 메서드 추가), 클래스는 package-private |
| 자식 클래스에 열기 | protected. 같은 패키지에도 열린다 |
protected. 자식 클래스에만 |
protected (자바 코드에는 같은 패키지까지 열림) |
| 밖에서는 읽기만 | private 필드 + public 게터 |
var + private set |
(기본 세터일 때) 세터 메서드 없음 |
| 생성자 막기 | private 생성자 |
private constructor() |
private 생성자 |
관련 항목
코틀린이 선언에 붙이는 가시성 수식어
가시성 수식어 · public · internal · protected · private
자바에서 같은 일을 하던 접근 제어 수단
접근 제어자 · package-private · 캡슐화 · 정보 은닉
internal 의 경계를 정하는 빌드 단위
모듈 · 패키지 · Gradle · 소스 세트 · Maven · IntelliJ IDEA
가시성이 클래스 파일에 번역된 모양
클래스 파일 · 이름 맹글링 · 정적 메서드 · 접근 플래그
가시성을 확인하는 도구
kotlinc · javac · 컴파일러 · javap
가시성을 따로 붙이는 코틀린 선언 요소
프로퍼티 · 게터 · 세터 · 주 생성자 · 톱레벨 함수 · 톱레벨 프로퍼티
protected 와 함께 움직이는 상속 문법
상속 · open · final · 오버라이드