중첩 클래스 (Nested)
한 클래스 내에 다른 클래스를 정의할 수 있다. 이러한 클래스를 중첩 클래스라고 한다.
class OuterClass {
...
class NestedClass {
...
}
static class StaticNestedClass {
...
}
}
중첩 클래스는 중첩 클래스를 포함하고 있는 클래스 멤버이다.
- 비정적(Non-static) 중첩 클래스 (내부 클래스)는 이 클래스를 포함하고 있는 외부 클래스의 다른 멤버에 접근할 수 있으며, 그 멤버들이 private으로 선언되어 있더라도 마찬가지이다.
- 정적(Static) 중첩 클래스는, 이 클래스를 포함하고 있는 외부 클래스의 다른 멤버에 접근할 수 없다.
- OuterClass의 멤버로서, 중첩 클래스는 private, public, protected 또는 package-private로 선언될 수 있다.
# 외부 클래스는 public 또는 package-private으로만 선언될 수 있다.
왜 중첩 클래스를 사용하는가?
한 곳에만 사용되는 클래스들을 논리적으로 그룹화하는 방법. 만약 어떤 클래스가 다른 클래스 하나에만 유용하다면, 그 클래스를 해당 클래스 내부에 내장시키고 두 클래스를 함께 유지하는 것이 논리적이다.
이러한 helper 클래스들을 중첩시키면 패키지가 더욱 간결해진다.
캡슐화 증가. 두 개의 최상위 클래스 A와 B가 있다고 가정할때, 클래스 B는 클래스 A의 멤버에 접근해야 하지만, 이 멤버들이 원래 private로 선언되어야 한다고 가정한다. 클래스 B를 클래스 A 내부에 숨김으로써, A 멤버들을 private로 선언할 수 있고 B는 이 멤버들에게 접근할 수 있다. 또한 B 자체도 외부 세계로부터 숨겨질 수 있다.
public class A { // 최상위 클래스 A
private int secretData;
public A(int data) {
this.secretData = data;
}
public int getSecretData() { // A의 private 멤버에 접근할 수 있는 메서드 제공
return secretData;
}
}
public class B { // 최상위 클래스 B
private A a;
public B(A a) {
this.a = a;
}
public void showSecretData() {
// B는 A의 private 멤버에 직접 접근할 수 없고
// A의 Getter 메서드를 통해서만 가능
System.out.println(a.getSecretData());
}
}
위 코드에서 B 클래스는 A 클래스의 secretData 필드에 접근할 수 없다. secretData 필드는 private로 선언되어 외부 클래스에서 접근할 수 없기 떄문에 A 클래스의 Getter 메서드를 호출해서 serectData에 액세스가 가능하다.
아래의 코드에서는 클래스 B를 클래스 A 내부에 숨길 수 있다.
public class A {
private int secretData;
public A(int data) {
this.secretData = data;
}
public int getSecretData() { // A의 private 멤버에 접근할 수 있는 메서드 제공
return secretData;
}
public class B { // B 클래스를 A 클래스 내부로 숨김
public void showSecretData() {
// B 클래스는 A 클래스의 private 멤버에 직접 접근할 수 있음
System.out.println(secretData);
}
}
public B createBInstance() {
return new B();
}
}
public class Main {
public static void main(String[] args) {
A a = new A(42);
A.B b = a.createBInstance();
b.showSecretData(); // 출력: 42
}
}
package com.intheeast.java;
import com.intheeast.java.A.B;
public class Main {
public static void main(String ... args) {
A a = new A(3);
B b = a.new B();
b.showSecretData();
}
}
import 구문으로 A와 B 클래스에 접근하였다.
이러한 중첩 메서드 사용은 더 읽기 싶고 유지보수하기 쉬운 코드로 이어질 수 있다.
최상위 클래스 내부에 작은 클래스들을 중첩시키면, 코드를 사용하는 곳에 더 가깝게 배치할 수 있다.
내부 클래스 (Inner Classe)
인스턴스 메소드와 변수와 마찬가지로, 내부 클래스는 이 클래스를 포함하는 외부 클래스의 인스턴스와 연관되어 있으며, 내부 클래스는 외부 클래스 객체의 메서드와 필드에 직접 접근할 수 있다. 하지만 내부 클래스는 외부 클래스 인스턴스와 연관되어 있기 떄문에, 스스로 어떤 정적 멤버도 정의할 수 없다.
내부 클래스의 인스턴스 객체들은 외부 클래스의 인스턴스 내부에 존재한다.
package com.intheeast.java;
public class OuterClass {
private int outerField;
private InnerClass innerClassInstance; // InnerClass 객체 참조 변수
public OuterClass(int outerField) {
this.outerField = outerField;
this.innerClassInstance = new InnerClass(); // 기본 생성자로 초기화
}
// 외부 클래스의 인스턴스 메서드
public void outerMethod() {
System.out.println("Outer class method.");
}
// 비정적 내부 클래스
public class InnerClass {
private int innerField;
// 디폴트 생성자
public InnerClass() {
this.innerField = 0;
}
// 파라미터를 받는 생성자
public InnerClass(int innerField) {
this.innerField = innerField;
}
// 내부 클래스의 메서드
public void innerMethod() {
// 내부 클래스는 외부 클래스의 필드와 메서드에 접근할 수 있음
System.out.println("Outer field: " + outerField);
outerMethod();
// 내부 클래스의 자체 필드에 접근
System.out.println("Inner field: " + innerField);
// this 키워드 사용
System.out.println("Inner class this: " + this);
System.out.println("Outer class this: " + OuterClass.this);
}
// 내부 클래스에서 외부 클래스의 메서드를 호출하는 메서드
public void callOuterMethod() {
OuterClass.this.outerMethod();
}
// 외부 클래스의 필드를 수정하는 메서드
public void modifyOuterField(int newValue) {
OuterClass.this.outerField = newValue;
System.out.println("Outer field modified to: " + outerField);
}
}
// OuterClass에서 InnerClass의 메서드 호출
public void callInnerMethod() {
innerClassInstance.innerMethod();
}
// OuterClass에서 InnerClass 인스턴스를 변경하는 메서드
public void setInnerClassInstance(int innerField) {
this.innerClassInstance = new InnerClass(innerField);
}
// OuterClass에서 InnerClass 인스턴스를 생성하는 메서드
public InnerClass createInnerClassInstance(int innerField) {
return new InnerClass(innerField);
}
// OuterClass의 main 메서드
public static void main(String[] args) {
OuterClass outer = new OuterClass(10);
// 기본 생성자를 사용하여 비정적 내부 클래스의 인스턴스 생성
InnerClass inner1 = outer.new InnerClass();
inner1.innerMethod();
// 매개변수를 받는 생성자를 사용하여 비정적 내부 클래스의 인스턴스 생성
InnerClass inner2 = outer.new InnerClass(20);
inner2.innerMethod();
// 외부 클래스의 메서드를 내부 클래스에서 호출
inner2.callOuterMethod();
// 내부 클래스에서 외부 클래스의 필드를 수정
inner2.modifyOuterField(100);
// 외부 클래스에서 내부 클래스의 메서드 호출
outer.callInnerMethod();
// 외부 클래스에서 내부 클래스의 인스턴스를 변경
outer.setInnerClassInstance(50);
outer.callInnerMethod();
// 외부 클래스에서 내부 클래스의 인스턴스를 생성하는 메서드 사용
InnerClass inner3 = outer.createInnerClassInstance(30);
inner3.innerMethod();
}
}
package com.intheeast.java;
public class TestClass {
public static void main(String[] args) {
// OuterClass 인스턴스 생성
OuterClass outer = new OuterClass(10);
// 기본 생성자를 사용하여 비정적 내부 클래스의 인스턴스 생성
OuterClass.InnerClass inner1 = outer.new InnerClass();
inner1.innerMethod();
// 매개변수를 받는 생성자를 사용하여 비정적 내부 클래스의 인스턴스 생성
OuterClass.InnerClass inner2 = outer.new InnerClass(20);
inner2.innerMethod();
// 외부 클래스의 메서드를 내부 클래스에서 호출
inner2.callOuterMethod();
// 내부 클래스에서 외부 클래스의 필드를 수정
inner2.modifyOuterField(100);
// 외부 클래스에서 내부 클래스의 메서드 호출
outer.callInnerMethod();
// 외부 클래스에서 내부 클래스의 인스턴스를 변경
outer.setInnerClassInstance(50);
outer.callInnerMethod();
// 외부 클래스에서 내부 클래스의 인스턴스를 생성하는 메서드 사용
OuterClass.InnerClass inner3 = outer.createInnerClassInstance(30);
inner3.innerMethod();
}
}
내부 클래스의 인스턴스는 외부 클래스의 인스턴스 내부에서만 존재할 수 있다.
내부 클래스의 인스턴스를 포함하는 내부 클래스 인스트의 메서드와 필드에 직접 접근할 수 있다.
내부 클러스를 인스턴스화하려면, 먼저 외부 클래스를 인스턴스화해야 한다.
아래의 구문을 통해 외부 객체 내부에 내부 객체를 생성할 수 있다.
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
두 가지의 특별한 inner 클래스로 local 클래스와 anonymous 클래스가 있다.
정적 중첩 클래스 (Static Nested)
클래스 메서드와 필드처럼, 정적 중첩 클래스는 정적 중첩 클래스의 외부 클래스와 연관되어 있다. 그리고 정적 클래스 메서드처럼, 정적 중첩 클래스는 직접적으로 정척 중첩 클래스를 포함하난 외부 클래스에서 정의된 인스턴스 필드나 메소드를 참조할 수 없다.
오직 객체 참조를 통해서만 사용할 수 있다.
정적 중첩 클래스는 다른 최상위 클래스들처럼 정적 중첩 클래스를 포함하는 외부 클래스의 인스턴스 멤버와 상화작용한다. 정적 중첩 클래스는 행독적으로는 최상위 클래스이지만, 패키징 편의를 위해 다흔 최상위 클래스에 중첩된 클래스이다.
정적 중첩 클래스를 인스턴스화 하는 방법은 최상위 클래스의 인스턴스화 방법과 동일하다.
StaticNestedClass staticNestedObject = new StaticNestedClass();
내부 클래스와 정적 중첩 클래스 예제
OuterClass,와 TopLevelClass는 외부 클래스(OuterClass)의 멤버 중 어떤 것들이 내부 클래스(InnerClass), 중첩 정적 클래스(StaticNestedClass), 최상위 클래스(TopLevelCalss)에서 접근할 수 있는지 보여준다.
package com.intheeast.java;
public class OuterClass {
String outerField = "Outer field";
static String staticOuterField = "Static outer field";
// StaticNestedClass 객체 참조 변수
StaticNestedClass staticNestedInstance;
public OuterClass() {
// StaticNestedClass 인스턴스를 생성하여 필드에 저장
staticNestedInstance = new StaticNestedClass();
}
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);
}
}
// StaticNestedClass의 메서드를 호출하는 메서드
public void callStaticNestedMethod() {
staticNestedInstance.accessMembers(this);
}
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("\nStatic nested class accessed from OuterClass:");
System.out.println("---------------------------------------------");
outerObject.callStaticNestedMethod();
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) {
System.out.println(outer.outerField);
System.out.println(OuterClass.staticOuterField);
}
}
Inner class:
------------
Outer field
Static outer field
Static nested class:
--------------------
Outer field
Static outer field
Top-level class:
--------------------
Outer field
Static outer field
정적 중첩 클래스는 다른 최상위 클래스처럼 외부 클래스의 인스턴스 멤버와 상호작용한다.
정적 중첩 클래스은 StaticNestedClass는 외부 클래스인 OuterClass의 인스턴스 필드인 outerField에 직접 접근할 수 없다.
static class StaticNestedClass {
void accessMembers(OuterClass outer) {
System.out.println(outerField); // 에러가 발생하는 부분
}
}
그렇기에 객체 참조를 통하여 outerField에 접근해야한다.
System.out.println(outer.outerField);
마찬가지로 최상위 클래스인 TopLevelClass도 outField에 직접 접근할 수 없다.
Shadowing
특정 범위 내에서 타입이나 멤버 변수 또는 파라미터 이름과 같은 선언이 바깥쪽 범위 내의 다른 선언과 동일한 이름을 가지면, 해당 선언은 바깥쪽 범위의 선언을 가린다.
그림자진(Shadowed) 선언을 단독으로 그 이름을 참조할 수는 없다.
특정 범위의 예시로는 내부 클래스 또는 메소드 정의가 있다.
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의 멤버변수, 세 번째는 methodFirstLevel 메서드의 파라미터이다.
메서드 methodFirstLevel의 파라미터로 정의된 x는 내부 클래스 FirstLevel의 변수를 가린다. 따라서 methodFirstLevel 메서드에서 변수 x를 사용하려면, 그것이 메서드 파라미터를 참조한다는 것을 알리는 this 키워드를 사용해야 한다.
System.out.println("this.x = " + this.x);
더 큰 범위를 포함하는 멤버 변수는 소속된 클래스의 이름을 사용하여 참조한다.
아래 코드는 methodInFirstLevel 메서드에서 클래스 ShadowTest의 멤버 변수에 접근한다.
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
직렬화 (Serialization)
내부 클래스 (Local 클래스 및 anonymous 클래스 포함)의 직렬화는 권장되지 않는다. 자바 컴파일러가 내부 클래스와 같은 특정 구조를 컴파일힐 떼, 소스 코드에 해당 구조가 없는 합성 구조를 생성한다. 합성 구조에는 클래스, 메서드, 필드 및 기타 구조가 포함된다. 합성 구조는 JVM의 변경 없이 새로운 자바 컴파일러 구현 간애 차이가 있을 수 있다.
즉 클래스 파일이 서로 다른 구현 간에는 다를 수 있다. 따라서 내부 클래스를 직렬화한 후 다른 JRE 구현으로 역직렬화할 경우 호환성 문제가 발생할 수 있다.
내부 클래스 예제
아래의 DataStructue.java 예제의 구성은 다음과 같다.
- DataStructue 외부 클래스는 연속된 정수 값(0,1,2,3...)으로 채워진 배열을 포함하는 DataStructue의 인스턴스를 생성하기 위한 생성자와 짝수 인덱스 값을 가진 배열 요소들을 출력하는 메서드를 포함한다.
- EvenIterator 내부 클래스는 DataStructueIterator 인터페이스를 구현하며, 이 인터페이스는 Interator<Integer> 인터페이스를 확장한다. 이터레이터는 데이터 구조를 순회하는데 사용되며, 일반적으로 마지막 요소를 테스트하는 메소드, 현재 요소를 검색하는 메소드, 다음 요소로 이동하는 메소드를 가지고 있다.
- main 메소드는 DataStructue 객체를 인스턴스화한 다음, 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 14
EvenIterator 클래스는 DataStructure 객체의 arrayOfInts 인스턴스 변수를 직접 참조한다는 점을 유의해야 한다.
위의 예제에서 보여준 것처럼, 내부 클래스를 사용하여 도우미 클래스와 같은 것들을 구현할 수 있다. 사용자 인터페이스 이벤트를 처리하기 위해서는 내부 클래스의 사용 방법을 알아야한다.
왜냐하면 이벤트 처리 매커니즘은 내부 클래스를 광범위하게 사용하기 때문이다.
수업은 DataStructure.java 코드 설명까지 진행되었다.
로컬 클래스와 익명 클래스 (Local and Anonymous)
내부 클래스에는 두 가치 추가적인 유형이 있다. 메소드의 본문 내에 내부 클래스를 선언할 수 있다. 이러한 클래스는 로컬 클래스로 알려져있다. 또한 클래스의 이름을 지정하지 않고 메서드 본문 내에 내부 클래스를 선언할 수도 있다. 이러한 클래스를 익명 클래스라한다.
수정자
내부 클래스에서는 외부 클래스의 다른 멤버들에 사용하는 것과 동일한 수정자를 사용할 수 있다.
액세스 지정자인 private, public 및 protected를 사용하여 내부 클래스에 대한 접근을 제한할 수 있다.
지역 클래스 (Local)
로컬 클래스는 균형 잡힌 중괄호 사이에 있는 하나 이상의 명령문 그룹인 블록 내에 정의된 클래스이다.
일반적으로 메소드 본문 내에서 로컬 클래스를 정의하는 것을 볼 수 있다.
지역 클래스 선언
어떤 블록 내에서도 지역 클래스를 정의할 수 있다.
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");
}
}
First number is 1234567890
Second number is invalid
Accessing Members of an Enclosing Class
로컬 클래스는 이 클래스를 포함하는 클래스의 멤버에 접근할 수 있다.
또한 로컬 클래스는 로컬 변수에도 접근할 수 있다. 하지만 로컬 클래스는 final로 선언된 로컬 변수에만 접근할 수 있다.
로컬 클래스가 이 클래스에 포함하는 블록의 로컬 변수나 파라미터에 접근할 때, 그 변수나 파라미터를 캡처한다.
JAVA SE 8부터 로컬 클래스는 포함 블록의 final 또는 실질적으로 final인 로컬 변수와 파라미터에 접근할 수 있습니다.
초기화된 후에 결코 변경되지 않는 변수나 파라미터는 실질적으로 final이다.
final로 선언된 변수에 접근하면 컴파일러 오류가 발생한다.
JAVA SE 8부터 메서드 내에서 로컬 클래스를 선언하면, 해당 클래스는 메소드의 파라미터에 접근할 수 있다.
지역 클래스와 유사한 내부 클래스
로컬 클래스는 어떠한 정적 멤버도 정의하거나 선언할 수 없다. 그러기에 내부 클래스와 유사하다.
멤버 변수로 정의하지 않으면 컴파일러 오류가 발생한다.
로컬 클래스는 로컬 클래스를 포함하는 블록의 인스턴스 멤버에 접근할 수 있기 때문에 비정적이다.
블록 내에서 인터페이스를 선언할 수 없다. 인터페이스는 본질적으로 정작이다.
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();
}
}
로컬 클래스 내에서는 static 초기화 블록이나 멤버 인터페이스를 선언할 수 없습니다.
아래의 예제는 메서드가 static로 선언되어 있기 때문에 컴파일 되지 않는다.
컴파일러는 이 메소드 정의를 만나면 컴파일러 오류를 생성한다.
public void sayGoodbyeInEnglish() {
class EnglishGoodbye {
public static void sayGoodbye() {
System.out.println("Bye bye");
}
}
EnglishGoodbye.sayGoodbye();
}
로컬 클래스는 상수 변수인 경우에 한해 정적 멤버를 가질 수 있다. 상수 변수는 기본 타입이거나 String 타입으로 final로 선언되고 컴파일 타임 상수 표현식으로 초기화된 변수이다.
컴파일 타임 상수 표현식은 일반적으로 컴파일 시단에 평가될 수 있는 문자열이나 산술 표현식이다.
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();
}
위 코드는 정적 멤버 EnglishGoodbye.farewell이 상수 변수이기 때문에 컴파일 된다.
익명 클래스
코드를 더 간결하게 만들 수 있다. 익명 클래스는 클래스 선언과 인스턴스화를 동시에 할 수 있게 한다.이름이 없는 것을 제이하면 지역 클래스와 유사하다.
지역 클래스를 한 번만 사용할 경우 익명 클래스를 사용하는 것이 좋다.
인터페이스 구현체, 추상 메서드의 Body를 완성하는데 사용된다.
익명 클래스 선언
지역 클래스가 클래스 선언인 반면, 익명 클래스는 표현식이다. 이는 다른 표현식 내에서 클래스를 정의한다는 의미이다.
다음 예제는 지역 클래스 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();
}
}
EnglishGreeting 클래스는 지역 클래스이고, frenchGreeting과 spanishGreeting은 익명 클래스로 초기화된다.
익명 클래스는 이름이 없으며 클래스 선언과 동시에 인스턴스화 된다.
이는 특정 클래스를 한 번만 사용할때 유용하다.
englishGreeting, frenchGreeting, spanishGreeting의 각 클래스는 HelloWorld 인터페이스를 구현하며, 각 언어로 인사를 출력하는 메서드를 포함한다.
main 메서드는 HelloWorldAnonymousClass의 인스턴스를 생성하고 sayHello 메서드를 호출하여 메시지를 출력한다.
익명 클래스의 Syntax
익명 클래스는 표현식이기 때문에 익명 클래스 표현식의 Syntax는 코드 블록에 클래스 정의가 포함되어 있다는 점을
제외하면 생서자 호출과 유사하다.
아래의 코드는 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 연산자
- 구현할 인터페이스 또는 확장할 클래스의 이름
- 일반 클래스 인스턴스 생성 표현식과 마찬가지로 생성자에 대한 매게변수를 포함하는 괄호이다.
- 클래스 선언, 본문에는 메서드 선언이 허용되지만 표현식은 허용되지 않는다.
# 인터페이스를 구현할때는 생성자가 없으므로 빈 괄호 쌍을 사용한다.
public class MyClass {
// 잘못된 문: 클래스 본문 안에 직접 문(statement)이 올 수 없습니다.
int number = 5;
System.out.println("Hello, World!"); // 컴파일 오류
// 생성자 선언
public MyClass() {
// 올바른 문: 생성자 본문 안에서는 문(statement)이 허용됩니다.
number = 10;
System.out.println("Hello, Constructor!");
}
// 메서드 선언
public void myMethod() {
// 올바른 문: 메서드 본문 안에서는 문(statement)이 허용됩니다.
System.out.println("Hello, Method!");
}
}
익명 클래스이 정의는 표현식이므로 명령문의 일부여야 한다.
익명 클래스의 표현식은 객체를 인스턴스화하는 명령문의 일부이기 때문에 마지막 닫는 중괄호에 세미콜론(;)를 붙인다.
Accessing Local Variables of the Enclosing Scope.
and Declaring and Accessing Members of Anonymous Class
로컬 클래스와 마찬가지로 익명 클래스도 변수를 캡쳐할 수 있다. 그들을 둘러싸는 범위를 지역 변수에 대해 동일한 액세스 권한을 가진다.
- 익명 클래스는 자신을 둘러싸는 클래스의 멤버에 액세스할 수 있습니다.
- 익명 클래스는 final 또는 사실상 final으로 선언되지 않는 바깥쪽 범위의 지역 변수에 액세스할 수 없다.
- 중첩 클래스와 마찬가지로 익명 클래스의 유형 선언은 바깥쪽 범뮈에서 동일한 이름을 가진 모든 선언을 숨긴다.
public class OuterClass {
public void myMethod() {
final int number = 10; // final로 선언됨
int number = 10; // <- JAVA SE8부터 사용 가능해짐
// 익명 클래스 선언
Runnable runnable = new Runnable() {
@Override
public void run() {
// number 변수에 접근 가능
System.out.println(number);
}
};
runnable.run();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.myMethod();
}
}
익명 클래스에서는 멤버와 관련하여 로컬 클래스
익명 클래스에서는 다음을 선언할 수 있다.
- 필드
- 추가 메서드(추상화시)
- 인스턴스 초기화
- 로컬 클래스 (추상화시)
하지만 익명 클래스에서는 생성자를 선언할 수 없다.
익명 클래스 예제는 자바 GUI 관련이기 때문에 패스했다.
'자바 튜토리얼' 카테고리의 다른 글
인터페이스 (Interface) (0) | 2024.07.08 |
---|---|
클래스와 객체 (Classes and Objects) [4] (0) | 2024.07.05 |
클래스와 객체 (Classes and Objects) [2] (0) | 2024.07.03 |
클래스와 객체 (Classes and Objects) [1] (0) | 2024.07.02 |
객체 지향 프로그래밍 (Object-Oriented Programming) (0) | 2024.07.02 |