25편. 중첩 클래스(Nested Class)
중첩 클래스(Nested classes)
중첩 클래스는 말 그대로 클래스 내에 정의된 클래스를 말합니다. 어떤 클래스가 한 곳에서만 쓰인다면 아래와 같이 해당 클래스를 중첩시키고 두 클래스를 한꺼번에 관리하는 것이 적절합니다.
class OuterClass { // 외부 클래스
// ...
class NestedClass { // 중첩 클래스
// ...
}
}
중첩 클래스는 다시 static으로 선언되지 않은 중첩 클래스인 내부 클래스(inner class)와 static으로 선언된 중첩 클래스인 정적 클래스(static class)로 나뉩니다. 여기서는 두 용어를 구분하도록 하겠습니다.
class OuterClass { // 외부 클래스
// ...
class InnerClass { // 내부 클래스
// ...
}
static class StaticNestedClass { // 정적 클래스
// ...
}
}
내부 클래스(Inner class)
내부 클래스는 자신을 둘러싸는 외부 클래스의 인스턴스 변수와 인스턴스 메서드에 접근할 수 있습니다. 내부 클래스도 외부 클래스에 안에 있는 것이므로 아래와 같이 외부 클래스의 private 멤버에 접근할 수 있습니다.
class OuterClass {
private int a = 10;
class InnerClass {
public void print() {
System.out.println("OuterClass.a: " + a);
}
}
}
그리고 내부 클래스를 인스턴스화하려면 먼저 외부 클래스를 인스턴스화해야 합니다.
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
아래와 같이 작성하면 에러가 발생하므로 주의하세요. 먼저 내부 클래스를 둘러싸는 외부 클래스를 인스턴스화한 후에 내부 클래스를 인스턴스화할 수 있습니다.
OuterClass.InnerClass innerObject = new OuterClass.InnerClass();
내부 클래스는 외부 클래스의 멤버이기도 하므로 접근 제한자(private, public, protected, default)를 사용할 수 있습니다.
class OuterClass {
private class InnerClass {
// ...
}
}
섀도잉(Shadowing)
외부 클래스의 필드나 메서드와 동일한 이름으로 내부 클래스의 필드나 메서드를 선언할 경우, 외부 클래스의 필드나 메서드가 그림자처럼 가려지는 것을 말합니다.
class OuterClass {
private int a = 10;
class InnerClass {
private int a = 20;
public void print() {
System.out.println(a); // 20
}
}
}
만약에 외부 클래스의 멤버에 접근하고 싶다면 아래와 같이 명시적으로 나타내야 합니다.
System.out.println(OuterClass.this.a);
정적 멤버 선언
내부 클래스에서는 정적 멤버를 정의하거나 선언할 수 없습니다. 선언하려하면 아래와 같은 에러가 발생합니다.
class Foo {
public void doSomething() {
class Bar {
// 에러: 필드 x는 상수 식(constant expression)으로 초기화되지 않은 경우
// 내부 클래스에서 static으로 선언할 수 없습니다.
static int x = 10;
// 에러: 메서드 doSomething()은 static으로 선언할 수 없습니다.
public static void doSomething() {
// ...
}
}
}
}
다만 정적 필드는 아래와 같이 final을 달면 선언할 수는 있습니다.
class Foo {
public void doSomething() {
class Bar {
static final int x = 10;
// ...
}
}
}
하지만 자바의 버전이 올라가면서 설계자들이 이런 제한을 제거할 필요성을 느끼면서, 자바 16부터는 내부 클래스에서 정적 멤버를 선언할 수 있게 되었습니다.
로컬 클래스(Local classes)
로컬 클래스는 블록 내에 정의된 내부 클래스입니다. 예를 들면, 메서드 내부, 반복문 내부, if문 내부에서 로컬 클래스를 정의할 수 있습니다. 로컬 클래스는 자신을 둘러싸는 블록에서만 접근할 수 있습니다.
class OuterClass {
public void doSomething() {
class LocalClass { // 로컬 클래스
public void doSomething() {
// ...
}
}
LocalClass obj = new LocalClass();
obj.doSomething();
}
}
로컬 클래스는 패키지나 클래스의 멤버가 아니므로 접근 제어자(private, public, protected)는 사용할 수 없습니다. 그리고 로컬 클래스는 자신을 둘러싸는 클래스의 멤버에 접근할 수 있습니다.
지역 변수로의 접근
메서드의 지역 변수에도 접근할 수 있지만 이 경우에는 해당 지역 변수가 final로 선언되어야 합니다. 하지만 자바 8부터는 사실상 final(effectively final)인 지역 변수에도 접근할 수 있게 되었습니다.
사실상 final(effectively final)
말 그대로 사실상 final입니다. final로 선언되지는 않았지만 초기화된 후에도 값이 변경되지 않는 변수나 매개변수는 사실상 final(effectively final)이라고 할 수 있습니다.
class Foo {
public void doSomething() {
int x = 10; // effectively final
class Bar {
public void doSomething() {
System.out.println(x);
}
}
// ...
}
}
하지만 아래와 같은 경우는 사실상 final이라고 할 수 없습니다. 초기화된 후에 값이 변경되었기 때문입니다.
class Foo {
public void doSomething() {
int x = 10;
// ...
x = 20; // 중간에 값이 한 번 변경됨
class Bar {
public void doSomething() {
// 지역 변수 x는 final이나 effectively final이어야 에러가 발생하지 않는다.
System.out.println(x);
}
}
// ...
}
}
정적 클래스(Static classes)
중첩 클래스는 static으로 선언할 수 있습니다. 정적 클래스는 내부 클래스와는 다르게 외부 클래스의 인스턴스 변수나 인스턴스 메서드에 접근할 수 없습니다. 정적 클래스는 외부 클래스를 인스턴스화할 필요가 없기 때문입니다.
class OuterClass {
static class StaticNestedClass {
public void doSomething() {
// ...
}
}
}
class JavaTutorial25 {
public static void main(String[] args) {
OuterClass.StaticNestedClass obj = new OuterClass.StaticNestedClass();
obj.doSomething();
}
}
익명 클래스(Anonymous classes)
익명 클래스는 이름이 없는 로컬 클래스입니다. 이름이 없기 때문에 익명 클래스를 가지고 객체를 여러 번 생성할 수 없으며 생성자를 만들 수도 없습니다. 익명 클래스는 클래스가 한 번만 필요하고, 가독성을 해치지 않을 정도로 클래스의 본문이 짧다면 사용을 고려해볼 수 있습니다.
부모 클래스 상속
부모 클래스를 상속하는 익명 클래스는 아래와 같이 만들 수 있습니다.
new 부모클래스(생성자 매개변수) {
// 클래스 몸체
}
예를 들어서 아래의 익명 클래스가 있다고 해봅시다.
class Foo { /* ... */ }
class JavaTutorial25 {
public static void main(String[] args) {
new Foo() {
public void doSomething() {
// ...
}
}.doSomething();
}
}
이는 다음과 같이 바꿀 수 있습니다.
class Foo { /* ... */ }
class AnonymousClass extends Foo {
public void doSomething() {
// ...
}
}
class JavaTutorial25 {
public static void main(String[] args) {
new AnonymousClass().doSomething();
}
}
인터페이스 구현
인터페이스를 구현하는 익명 클래스는 아래와 같이 만들 수 있습니다.
new 인터페이스() {
// 클래스 몸체
}
예를 들어서 아래의 익명 클래스가 있다고 해봅시다.
class JavaTutorial25 {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
// ...
}
}).start();
}
}
이는 다음과 같이 바꿀 수 있습니다.
class AnonymousClass implements Runnable {
public void run() {
// ...
}
}
class JavaTutorial25 {
public static void main(String[] args) {
new Thread(new AnonymousClass()).start();
}
}
'프로그래밍 관련 > 자바' 카테고리의 다른 글
34편. 애노테이션(Annotation) (0) | 2022.02.18 |
---|---|
26편. 제네릭(Generic) (0) | 2022.02.06 |
29편. 스레드(Thread) (2) (1) | 2022.01.27 |
정리. JVM의 힙 영역 살펴보기 (0) | 2022.01.22 |
14편. 문자열(String) (2) | 2022.01.15 |