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 |
댓글을 사용할 수 없습니다.