중첩 클래스
Java 프로그래밍 언어에서는 다른 클래스 안에 클래스를 정의할 수 있습니다. 이러한 클래스를 중첩 클래스라고 하며 여기에는 이러한 클래스가 설명되어 있습니다:
class OuterClass {
...
class NestedClass {
...
}
}용어: 중첩 클래스는 비정적(non-static)과 정적(static)의 두 가지 범주로 나뉩니다. Non-static 중첩 클래스를 내부 클래스(inner classes) 라고 합니다. static으로 선언된 중첩 클래스는 정적 중첩 클래스(static nested classes) 라고 합니다.
class OuterClass {
...
class InnerClass {
...
}
static class StaticNestedClass {
...
}
}중첩 클래스는 둘러싸고 있는 클래스의 멤버입니다. Non-static 중첩 클래스(내부 클래스)는 private으로 선언되어 있어도 둘러싸고 있는 클래스의 다른 멤버에 액세스할 수 있습니다. Static 중첩 클래스는 둘러싸고 있는 클래스의 다른 멤버에 액세스할 수 없습니다. OuterClass의 멤버로서 중첩 클래스는 private, public, protected 또는 패키지 비공개로 선언할 수 있습니다. 외부 클래스는 public 또는 패키지 비공개(default)로만 선언할 수 있다는 점을 기억하세요.
왜 중첩 클래스를 사용하나요?
중첩 클래스를 사용하는 강력한 이유는 다음과 같습니다:
- 한 곳에서만 사용되는 클래스를 논리적으로 그룹화하는 방법입니다: 어떤 클래스가 다른 클래스 하나에만 유용하다면 그 클래스에 포함시켜 두 클래스를 함께 유지하는 것이 논리적으로 맞습니다. 이러한 “헬퍼 클래스”를 중첩하면 패키지가 더욱 간소화됩니다.
- 캡슐화가 증가합니다: 두 개의 최상위 클래스인
A와B가 있는데,B는 비공개로 선언된A의 멤버에 액세스해야 한다고 가정해 봅시다. 클래스A내에 클래스B를 숨기면A의 멤버를private로 선언할 수 있고B는 이에 액세스할 수 있습니다. 또한B자체를 외부에서 숨길 수도 있습니다. - 이를 통해 더 읽기 쉽고 유지 관리하기 쉬운 코드를 만들 수 있습니다: 최상위 클래스 안에 작은 클래스를 중첩하면 코드가 사용되는 위치에 코드가 더 가깝게 배치됩니다.
내부 클래스
인스턴스 메서드 및 변수와 마찬가지로 내부 클래스는 둘러싸고 있는 클래스의 인스턴스와 연관되며 해당 객체의 메서드 및 필드에 직접 액세스할 수 있습니다. 또한 내부 클래스는 인스턴스와 연관되어 있기 때문에 정적 멤버 자체를 정의할 수 없습니다.
내부 클래스의 인스턴스인 객체는 외부 클래스의 인스턴스 내에 존재합니다. 다음 클래스를 생각해 보세요:
class OuterClass {
...
class InnerClass {
...
}
}내부 클래스의 인스턴스는 외부 클래스의 인스턴스 내에서만 존재할 수 있으며, 둘러싸는 인스턴스의 메서드와 필드에 직접 액세스할 수 있습니다.
내부 클래스를 인스턴스화하려면 먼저 외부 클래스를 인스턴스화해야 합니다. 그런 다음 이 구문을 사용하여 외부 객체 내에 내부 객체를 생성합니다:
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();내부 클래스에는 로컬 클래스와 익명 클래스라는 두 가지 특별한 종류가 있습니다.
정적 중첩 클래스
클래스 메서드 및 변수와 마찬가지로 정적 중첩 클래스는 외부 클래스와 연관됩니다. 그리고 정적 중첩 클래스는 정적 클래스 메서드와 마찬가지로 인스턴스 변수나 메서드를 직접 참조할 수 없으며, 객체 참조를 통해서만 사용할 수 있습니다. 내부 클래스와 중첩된 정적 클래스 예제가 이를 보여줍니다.
참고: 정적 중첩 클래스는 다른 최상위 클래스와 마찬가지로 외부 클래스(및 다른 클래스)의 인스턴스 멤버와 상호 작용합니다. 사실상 정적 중첩 클래스는 패키징 편의를 위해 다른 최상위 클래스에 중첩된 최상위 클래스입니다. 내부 클래스와 중첩된 정적 클래스 예시에서도 이를 보여줍니다.
정적 중첩 클래스는 최상위 클래스와 동일한 방식으로 인스턴스화합니다:
StaticNestedClass staticNestedObject = new StaticNestedClass();내부 클래스와 중첩된 정적 클래스 예제
다음 예제인 OuterClass는 TopLevelClass와 함께 OuterClass의 내부 클래스(InnerClass), 중첩된 정적 클래스(StaticNestedClass) 및 최상위 클래스(TopLevelClass)가 액세스할 수 있는 클래스 멤버를 보여줍니다:
OuterClass.java
public class OuterClass {
String outerField = "Outer field";
static String staticOuterField = "Static outer field";
class InnerClass {
void accessMembers() {
System.out.println(outerField);
System.out.println(staticOuterField);
}
}
static class StaticNestedClass {
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field outerField
// System.out.println(outerField);
System.out.println(outer.outerField);
System.out.println(staticOuterField);
}
}
public static void main(String[] args) {
System.out.println("Inner class:");
System.out.println("------------");
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
innerObject.accessMembers();
System.out.println("\nStatic nested class:");
System.out.println("--------------------");
StaticNestedClass staticNestedObject = new StaticNestedClass();
staticNestedObject.accessMembers(outerObject);
System.out.println("\nTop-level class:");
System.out.println("--------------------");
TopLevelClass topLevelObject = new TopLevelClass();
topLevelObject.accessMembers(outerObject);
}
}TopLevelClass.java
public class TopLevelClass {
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field OuterClass.outerField
// System.out.println(OuterClass.outerField);
System.out.println(outer.outerField);
System.out.println(OuterClass.staticOuterField);
}
}This example prints the following output:
Inner class:
------------
Outer field
Static outer field
Static nested class:
--------------------
Outer field
Static outer field
Top-level class:
--------------------
Outer field
Static outer field정적 중첩 클래스는 다른 최상위 클래스와 마찬가지로 외부 클래스의 인스턴스 멤버와 상호 작용합니다. 정적 중첩 클래스 StaticNestedClass는, outerField가 둘러싸는 클래스인 OuterClass의 인스턴스 변수이기 때문에 직접 액세스할 수 없습니다. Java 컴파일러는 강조 표시된 문에서 오류를 생성합니다.
static class StaticNestedClass {
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field outerField
System.out.println(outerField);
}
}이 오류를 해결하려면 객체 참조를 통해 outerField에 액세스하세요:
System.out.println(outer.outerField);마찬가지로 최상위 클래스인 TopLevelClass도 outerField에 직접 액세스할 수 없습니다.
섀도잉
특정 범위(예: 내부 클래스 또는 메서드 정의)의 타입 선언(예: 멤버 변수 또는 매개변수 이름)이 둘러싸는 범위의 다른 선언과 이름이 같은 경우 해당 선언은 둘러싸는 범위의 선언을 섀도잉합니다. 섀도 선언은 그 이름만으로는 참조할 수 없습니다. 다음 예제인 ShadowTest가 이를 보여줍니다:
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}다음은 이 예제의 출력입니다:
x = 23
this.x = 1
ShadowTest.this.x = 0이 예에서는 x라는 세 가지 변수를 정의합니다: ShadowTest 클래스의 멤버 변수, 내부 클래스 FirstLevel의 멤버 변수, 그리고 methodInFirstLevel() 메서드의 매개변수입니다. 메서드 methodInFirstLevel()의 매개변수로 정의된 변수 x는 내부 클래스 FirstLevel의 변수를 섀도잉합니다. 따라서 methodInFirstLevel() 메서드에서 변수 x를 사용하면 메서드 매개 변수를 참조합니다. 내부 클래스 FirstLevel의 멤버 변수를 참조하려면 this 키워드를 사용하여 둘러싸는 범위를 나타냅니다:
System.out.println("this.x = " + this.x);더 큰 범위를 포함하는 멤버 변수는 해당 변수가 속한 클래스 이름으로 묶습니다. 예를 들어, 다음 문은 methodInFirstLevel() 메서드에서 ShadowTest 클래스의 멤버 변수에 액세스합니다:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);직렬화
로컬 및 익명 클래스를 포함한 내부 클래스의 직렬화(Serialization)는 강력히 권장하지 않습니다. Java 컴파일러는 내부 클래스와 같은 특정 구문을 컴파일할 때 합성 구문(소스 코드에 해당 구문이 없는 클래스, 메서드, 필드 및 기타 구문)을 생성합니다. 합성 구문을 사용하면 Java 컴파일러는 JVM을 변경하지 않고도 새로운 Java 언어 기능을 구현할 수 있습니다.
그러나 합성 구조체는 Java 컴파일러 구현마다 다를 수 있으며, 이는 ‘.class’ 파일도 구현마다 다를 수 있음을 의미합니다. 따라서 내부 클래스를 직렬화한 다음 다른 JRE 구현으로 역직렬화하는 경우 호환성 문제가 발생할 수 있습니다.
내부 클래스 예제
사용 중인 내부 클래스를 확인하려면 먼저 배열을 생각해 보세요. 다음 예제에서는 배열을 생성하고 정수 값으로 채운 다음 배열의 짝수 인덱스의 값만 오름차순으로 출력합니다.
다음 DataStructure.java 예제는 다음과 같이 구성되어 있습니다:
- 연속된 정수 값(0, 1, 2, 3 등)으로 채워진 배열을 포함하는
DataStructure의 인스턴스를 생성하는 생성자와 인덱스 값이 짝수인 배열의 요소를 출력하는 메서드가 포함된DataStructure외부 클래스. EvenIterator내부 클래스는Iterator< Integer>인터페이스를 확장하는DataStructureIterator인터페이스를 구현하는EvenIterator입니다. 이터레이터는 데이터 구조를 단계적으로 처리하는 데 사용되며 일반적으로 마지막 요소를 테스트하고 현재 요소를 검색하고 다음 요소로 이동하는 메서드가 있습니다.DataStructure객체(ds)를 인스턴스화한 다음printEven()메서드를 호출하여 인덱스 값이 짝수인 배열arrayOfInts의 요소를 인쇄하는 메인 메서드입니다.
public class DataStructure {
// Create an array
private final static int SIZE = 15;
private int[] arrayOfInts = new int[SIZE];
public DataStructure() {
// fill the array with ascending integer values
for (int i = 0; i < SIZE; i++) {
arrayOfInts[i] = i;
}
}
public void printEven() {
// Print out values of even indices of the array
DataStructureIterator iterator = this.new EvenIterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
}
interface DataStructureIterator extends java.util.Iterator<Integer> { }
// Inner class implements the DataStructureIterator interface,
// which extends the Iterator<Integer> interface
private class EvenIterator implements DataStructureIterator {
// Start stepping through the array from the beginning
private int nextIndex = 0;
public boolean hasNext() {
// Check if the current element is the last in the array
return (nextIndex <= SIZE - 1);
}
public Integer next() {
// Record a value of an even index of the array
Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);
// Get the next even element
nextIndex += 2;
return retValue;
}
}
public static void main(String s[]) {
// Fill the array with integer values and print out only
// values of even indices
DataStructure ds = new DataStructure();
ds.printEven();
}
}출력은 다음과 같습니다:
0 2 4 6 8 10 12 14EvenIterator 클래스는 DataStructure 객체의 arrayOfInts 인스턴스 변수를 직접 참조한다는 점에 유의하세요.
내부 클래스를 사용하여 이 예제에 표시된 것과 같은 헬퍼 클래스를 구현할 수 있습니다. 사용자 인터페이스 이벤트를 처리하려면 이벤트 처리 메커니즘이 내부 클래스를 광범위하게 사용하기 때문에 내부 클래스 사용법을 알아야 합니다.
로컬 및 익명 클래스
내부 클래스에는 두 가지 추가 타입이 있습니다. 메서드 본문 내에서 내부 클래스를 선언할 수 있습니다. 이러한 클래스를 로컬 클래스라고 합니다. 클래스 이름을 지정하지 않고 메서드 본문 내에 내부 클래스를 선언할 수도 있습니다. 이러한 클래스를 익명 클래스라고 합니다.
수정자
외부 클래스의 다른 멤버에 사용하는 것과 동일한 수정자를 내부 클래스에도 사용할 수 있습니다. 예를 들어, 다른 클래스 멤버에 대한 액세스를 제한하는 데 사용하는 것과 마찬가지로 접근 지정자 private, public 및 protected를 사용하여 내부 클래스에 대한 액세스를 제한할 수 있습니다.
로컬 클래스
로컬 클래스는 중괄호 사이에 0개 이상의 문으로 구성된 그룹인 블록에 정의된 클래스입니다. 로컬 클래스는 일반적으로 메서드 본문에서 정의됩니다.
이 섹션에서는 다음 주제를 다룹니다:
- 로컬 클래스 선언하기
- 둘러싸는 클래스의 멤버 액세스
- 섀도잉과 로컬 클래스
- 로컬 클래스는 내부 클래스와 유사합니다.
로컬 클래스 선언하기
블록 안에 로컬 클래스를 정의할 수 있습니다(자세한 내용은 표현식, 문 및 블록 참조). 예를 들어 메서드 본문, for 루프 또는 if 절에서 로컬 클래스를 정의할 수 있습니다.
다음 예제인 LocalClassExample은 두 개의 전화 번호의 유효성을 검사합니다. 이 예에서는 validatePhoneNumber() 메서드에서 로컬 클래스 PhoneNumber를 정의합니다:
public class LocalClassExample {
static String regularExpression = "[^0-9]";
public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) {
final int numberLength = 10;
// Valid in JDK 8 and later:
// int numberLength = 10;
class PhoneNumber {
String formattedPhoneNumber = null;
PhoneNumber(String phoneNumber){
// numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}
public String getNumber() {
return formattedPhoneNumber;
}
// Valid in JDK 8 and later:
// public void printOriginalNumbers() {
// System.out.println("Original numbers are " + phoneNumber1 +
// " and " + phoneNumber2);
// }
}
PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
// Valid in JDK 8 and later:
// myNumber1.printOriginalNumbers();
if (myNumber1.getNumber() == null)
System.out.println("First number is invalid");
else
System.out.println("First number is " + myNumber1.getNumber());
if (myNumber2.getNumber() == null)
System.out.println("Second number is invalid");
else
System.out.println("Second number is " + myNumber2.getNumber());
}
public static void main(String... args) {
validatePhoneNumber("123-456-7890", "456-7890");
}
}이 예에서는 먼저 전화번호에서 0부터 9까지의 숫자를 제외한 모든 문자를 제거하여 전화번호의 유효성을 검사합니다. 그런 다음 전화 번호에 정확히 10자리(북미의 전화번호 길이)가 포함되어 있는지 확인합니다. 이 예는 다음과 같이 출력합니다:
First number is 1234567890
Second number is invalid둘러싸는 클래스의 멤버에 접근하기
로컬 클래스는 둘러싸는 클래스의 멤버에 액세스할 수 있습니다. 앞의 예제에서 PhoneNumber() 생성자는 LocalClassExample.regularExpression 멤버에 액세스합니다.
또한 로컬 클래스는 로컬 변수에 액세스할 수 있습니다. 그러나 로컬 클래스는 final로 선언된 로컬 변수에만 액세스할 수 있습니다. 로컬 클래스가 둘러싸는 블록의 로컬 변수나 매개변수에 접근하면 해당 변수나 매개변수를 캡처합니다. 예를 들어, PhoneNumber() 생성자는 final로 선언된 로컬 변수 numberLength에 액세스할 수 있으며, numberLength는 캡쳐된 변수입니다.
그러나 Java SE 8부터는 로컬 클래스가 final 또는 effectively final 인 둘러싸는 블록의 로컬 변수 및 매개 변수에 액세스할 수 있습니다. 초기화된 후 값이 변경되지 않는 변수나 매개변수는 effectively final 입니다. 예를 들어 numberLength 변수가 final로 선언되지 않은 상태에서 PhoneNumber() 생성자에 강조 표시된 할당 문을 추가하여 유효한 전화 번호의 길이를 7자리로 변경한다고 가정해 보겠습니다:
PhoneNumber(String phoneNumber) {
numberLength = 7;
String currentNumber = phoneNumber.replaceAll(
regularExpression, "");
if (currentNumber.length() == numberLength)
formattedPhoneNumber = currentNumber;
else
formattedPhoneNumber = null;
}이 할당 문으로 인해 변수 numberLength는 더 이상 사실상 최종적이지(not effectively final) 않습니다. 결과적으로 Java 컴파일러는 내부 클래스 PhoneNumber가 numberLength 변수에 액세스하려고 할 때 “내부 클래스에서 참조하는 로컬 변수는 final 이거나 effectively final이어야 합니다.”와 유사한 오류 메시지를 생성합니다:
if (currentNumber.length() == numberLength)Java SE 8부터는 메서드에서 로컬 클래스를 선언하면 메서드의 매개 변수에 액세스할 수 있습니다. 예를 들어, PhoneNumber 로컬 클래스에서 다음 메서드를 정의할 수 있습니다:
public void printOriginalNumbers() {
System.out.println("Original numbers are " + phoneNumber1 +
" and " + phoneNumber2);
}printOriginalNumbers() 메서드는 validatePhoneNumber() 메서드의 매개 변수 phoneNumber1 및 phoneNumber2에 액세스합니다.
로컬 클래스에 있는 타입(예: 변수)의 선언은 같은 이름을 가진 둘러싸는 범위의 선언을 섀도잉합니다. 자세한 내용은 섀도잉을 참조하세요.
로컬 클래스는 내부 클래스와 유사합니다.
로컬 클래스는 정적 멤버를 정의하거나 선언할 수 없기 때문에 내부 클래스와 유사합니다. 정적 메서드 validatePhoneNumber()에 정의된 PhoneNumber 클래스와 같은 정적 메서드의 로컬 클래스는 둘러싸고 있는 클래스의 정적 멤버만 참조할 수 있습니다. 예를 들어, 멤버 변수 regularExpression을 static으로 정의하지 않으면 Java 컴파일러는 “정적 컨텍스트에서 정적이 아닌 변수 regularExpression을 참조할 수 없습니다.”와 유사한 오류를 생성합니다.
로컬 클래스는 둘러싸는 블록의 인스턴스 멤버에 액세스할 수 있기 때문에 정적이 아닙니다. 따라서 대부분의 정적 선언을 포함할 수 없습니다.
인터페이스는 본질적으로 정적이므로 블록 내부에 인터페이스를 선언할 수 없습니다. 예를 들어, 다음 코드 발췌문은 HelloThere 인터페이스가 greetInEnglish() 메서드의 본문 내부에 정의되어 있기 때문에 컴파일되지 않습니다:
public void greetInEnglish() {
interface HelloThere {
public void greet();
}
class EnglishHelloThere implements HelloThere {
public void greet() {
System.out.println("Hello " + name);
}
}
HelloThere myGreeting = new EnglishHelloThere();
myGreeting.greet();
}정적 초기화자나 멤버 인터페이스는 로컬 클래스에서 선언할 수 없습니다.
다음 코드 예제에서는 메서드 EnglishGoodbye.sayGoodbye()가 정적으로 선언되었기 때문에 컴파일되지 않습니다. 컴파일러는 이 메서드 정의를 만나면 “수정자 static은 상수 변수 선언에서만 허용됩니다.”와 유사한 오류를 생성합니다:
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static void sayGoodbye() {
System.out.println("Bye bye");
}
}
EnglishGoodbye.sayGoodbye();
}로컬 클래스는 상수 변수인 경우 정적 멤버를 가질 수 있습니다. (상수 변수는 final로 선언되고 compile-time 상수 표현식으로 초기화되는 원시 타입 또는 String 타입의 변수입니다. 컴파일 타임 상수 표현식은 일반적으로 컴파일 타임에 평가할 수 있는 문자열 또는 산술 표현식입니다. 자세한 내용은 클래스 멤버 이해하기를 참조하세요.) 다음 코드 발췌문은 정적 멤버인 EnglishGoodbye.farewell이 상수 변수이기 때문에 컴파일됩니다:
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static final String farewell = "Bye bye";
public void sayGoodbye() {
System.out.println(farewell);
}
}
EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
myEnglishGoodbye.sayGoodbye();
}
익명 클래스
익명 클래스를 사용하면 코드를 더 간결하게 만들 수 있습니다. 익명 클래스를 사용하면 클래스를 선언하고 동시에 인스턴스화할 수 있습니다. 이름이 없다는 점을 제외하면 로컬 클래스와 비슷합니다. 로컬 클래스를 한 번만 사용해야 하는 경우 익명 클래스를 사용하세요.
익명 클래스 선언하기
로컬 클래스는 클래스 선언인 반면 익명 클래스는 표현식으로, 다른 표현식으로 클래스를 정의하는 것을 의미합니다. 다음 예제인 HelloWorldAnonymousClasses는 로컬 변수 frenchGreeting과 spanishGreeting의 초기화 문에는 익명 클래스를 사용하지만 englishGreeting 변수의 초기화에는 로컬 클래스를 사용합니다:
public class HelloWorldAnonymousClasses {
interface HelloWorld {
public void greet();
public void greetSomeone(String someone);
}
public void sayHello() {
class EnglishGreeting implements HelloWorld {
String name = "world";
public void greet() {
greetSomeone("world");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hello " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}익명 클래스의 구문
앞서 언급했듯이 익명 클래스는 표현식입니다. 익명 클래스 표현식의 구문은 코드 블록에 클래스 정의가 포함되어 있다는 점을 제외하면 생성자 호출과 비슷합니다.
frenchGreeting 객체의 인스턴스화를 생각해 보겠습니다:
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};익명 클래스 표현식은 다음과 같이 구성됩니다:
new연산자- 구현할 인터페이스 또는 확장할 클래스의 이름입니다. 이 예제에서 익명 클래스는
HelloWorld인터페이스를 구현하고 있습니다. - 일반적인 클래스 인스턴스 생성 표현식과 마찬가지로 생성자에 대한 인수를 포함하는 괄호입니다. 참고: 인터페이스를 구현할 때는 생성자가 없으므로 이 예제에서와 같이 빈 괄호 쌍을 사용합니다.
- 본문은 클래스 선언 본문입니다. 보다 구체적으로, 본문에서는 메서드 선언은 허용되지만 문은 허용되지 않습니다.
- 익명 클래스 정의는 표현식이므로 문에 포함되어야 합니다. 이 예제에서 익명 클래스 표현식은
frenchGreeting객체를 인스턴스화하는 문의 일부입니다. (이것이 닫는 중괄호 뒤에 세미콜론이 있는 이유를 설명합니다.)
둘러싸는 범위의 로컬 변수 액세스 및 익명 클래스의 멤버 선언 및 액세스하기
로컬 클래스와 마찬가지로 익명 클래스도 변수를 캡처할 수 있으며, 둘러싸는 범위의 로컬 변수에 대한 액세스 권한이 동일합니다:
- 익명 클래스는 둘러싸는 클래스의 멤버에 액세스할 수 있습니다.
- 익명 클래스는 둘러싸는 범위의 로컬 변수 중 최종 또는 사실상 최종으로 선언되지 않은 변수에 액세스할 수 없습니다.
- 중첩 클래스와 마찬가지로 익명 클래스에서 타입(예: 변수)을 선언하면 둘러싸는 범위에서 같은 이름을 가진 다른 선언이 모두 섀도잉됩니다. 자세한 내용은 섀도잉을 참조하세요.
익명 클래스에도 멤버와 관련하여 로컬 클래스와 동일한 제한이 있습니다:
- 익명 클래스에는 정적 초기화자나 멤버 인터페이스를 선언할 수 없습니다.
- 익명 클래스는 상수 변수인 경우 정적 멤버를 가질 수 있습니다.
익명 클래스에서 다음을 선언할 수 있습니다:
- 필드
- 추가 메서드(슈퍼타입의 메서드를 구현하지 않더라도)
- 인스턴스 이니셜라이저
- 로컬 클래스
그러나 익명 클래스에서는 생성자를 선언할 수 없습니다.