1. 중첩 클래스(Nested Class)
중첩 클래스는 한 클래스 내부에서 정의한 클래스를 말한다. 중첩 클래스를 사용하면 두 클래스의 멤버들을 서로 쉽게 접근할 수 있고, 외부에는 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다.
- 인스턴스 멤버 클래스(inner class)
static 키워드 없이 중첩 선언된 클래스를 말한다. 인스턴스 멤버 클래스는 인스턴스 필드와 메소드만 선언이 가능하고 정적 필드와 메소드는 선언할 수 없다.
class OuterClass {
// 인스턴스 멤버 클래스
class InnerClass {
InnerClass {} // 생성자
int field1; // 인스턴스 필드
void method1() { // 인스턴스 메소드
System.out.println("inner method");
}
}
InnerClass i = new InnerClass(); // OuterClass 내에서 InnerClass 객체 생성
i.field = 3;
i.method1();
}
만약 OuterClass 외부에서 InnerClass 객체를 생성하기 위해서는 먼저 OuterClass 객체를 생성한 후 InnerClass 객체를 생성해야 한다.
OuterClass o = new OuterClass();
OuterClass.InnerClass i = o.new InnerClass();
i.field1 = 3;
i.method1();
- 정적 멤버 클래스(Static Nested Class)
Stiatic 메소드, Static 변수와 마찬가지로 Static Nested Class는 Outer 클래스에 정의된 인스턴스 변수나 메소드를 직접 참조할 수 없으며, 객체 참조를 통해서만 사용할 수 있다.
class OuterClass {
// 정적 멤버 클래스
static class staticClass {
staticClass {} // 생성자
int field1; // 인스턴스 필드
static int field2; // 정적 필드
void method1() {
System.out.println("인스턴스 메소드");
}
static void method2() {
System.out.println("정적 메소드);
}
}
}
OuterClass 외부에서 정적 멤버 클래스의 객체를 생성하기 위해서는 OuterClass 객체를 생성할 필요가 없다.
OuterClass.StaticClass s = new OuterClass.StaticClass();
s.field1 = 3; // 인스턴스 필드 사용
s.method1(); // 인스턴스 메소드 호출
OuterClass.StaticClass.field2 = 3; // 정적 필드 사용
OuterClass.StaticClass.method2(); // 정적 필드 호출
- 왜 중첩 클래스(Nested Class)가 필요할까?
중첩 클래스를 사용하게 되면 코드를 더 구조화하고 가독성을 높이며, 특정 클래스가 다른 클래스에 강하게 연관되거나 해당 클래스 내부에서만 사용되도록 범위를 제한할 수 있다.
예를 들어, Car 클래스에서 Engine을 사용한다고 가정하자. 중첩 클래스를 사용하지 않았을 때는 두 클래스를 따로 정의하게 된다.
public class Engine { // 엔진 클래스를 따로 생성
public void startEngine() {
System.out.println("엔진이 작동");
}
}
public class Car {
private String model;
private Engine engine;
// 생성자
public Car(String model) {
this.model = model;
this.engine = new Engine(); // 엔진 클래스 객체를 별도로 관리
}
public void start() {
engine.startEngine();
System.out.println(model + "이 출발");
}
}
public class Driver {
public static void main(String[] args) {
Car car = new Car("아우디 A8");
car.start();
}
}
중첩 클래스를 사용하게 되면 Car 클래스 내에 Engine 클래스를 생성한다.
public class Car {
private String model;
private Engine engine;
// 생성자
public Car(String model) {
this.model = model;
this.engine = new Engine();
}
public void start() {
engine.startEngine();
System.out.println(model + "이 출발");
}
private class Engine {
public void startEngine() {
System.out.println("엔진이 작동");
}
}
}
public class Driver {
public static void main(String[] args) {
Car car = new Car("아우디 A8");
car.start();
}
}
위 예시 코드를 보면서 중첩 클래스의 장점을 설명해보자
1. 논리적으로 연관된 관계를 명확히 표현 : Engine 클래스가 Car 클래스 내부에 있으면서 두 클래스의 밀접한 연관성을 코드에 반영할 수 있다.
2. 코드 캡슐화 및 보안 강화 : Engine 클래스를 외부에서 접근을 제한하면서 보호할 수 있다.
3. 코드 가독성과 유지보수성 향상 : Engine이 Car 내부에 포함되므로 코드를 읽는 사람이 Car와 Engine이 밀접한 관계가 있다는 것을 쉽게 이해할 수 있다. 또한, 관련된 클래스가 한 곳에 모여있어 유지보수시 편리하다.
4. 파일 관리의 단순화 : 별도의 파일을 분리할 필요 없이 Car 클래스 안에 정의 되므로 관리가 더 쉬워진다.
- 중첩 클래스(Nested Class)의 접근 제한
멤버 클래스 내부에서 외부 클래스의 필드와 메소드에 접근할 때는 제한이 따릅니다. 또한 메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때도 제한이 따른다.
1.외부 클래스의 필드와 메소드 접근
중첩 클래스는 외부 클래스의 모든 필드와 메소드(private로 선언된 멤버까지) 접근 가능하다. 그 이유는 중첩 클래스가 외부 클래스의 일부로 간주되기 때문이다.
public class Out {
private String field1 = "private-field";
public void method1() {
System.out.println("public-method")
}
public class Inner {
public void accessOut() {
System.out.println(field1);
method1();
}
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.Inner inner = Outer.new Inner();
inner.accessOut();
}
<결과>
private-field
public-method
2. 멤버 클래스에서의 접근 제한
멤버 클래스는 외부 클래스의 인스턴스 멤버로 동작하기 때문에, 외부 클래스의 모든 필드와 메소드에 접근 가능하다. 하지만 private로 선언된 멤버 클래스 같은 경우에는 외부 클래스 외부에서는 사용할 수 없다.
public class OuterClass {
private String secret = "Secret Data";
private class PrivateNestedClass {
public void printSecret() {
System.out.println("Accessing: " + secret);
}
}
public void accessPrivateNestedClass() {
PrivateNestedClass nested = new PrivateNestedClass();
nested.printSecret();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// 외부에서 직접 접근 불가
OuterClass.PrivateNestedClass nested = outer.new PrivateNestedClass(); // 오류 발생
outer.accessPrivateNestedClass(); // "Accessing: Secret Data" 출력
}
}
3. 로컬 클래스에서의 접근제한
로컬 클래스는 메소드 내에 정의되는 클래스이다. 로컬 클래스 내의 변수는 final이어야 하므로 한 번 선언하면 변경 불가하다.
public class OuterClass {
private String outerField = "Outer Field";
public void outerMethod() {
String localVar = "Local Variable"; // 반드시 final이어야 함
class LocalClass {
public void printFields() {
System.out.println(outerField); // 바깥 클래스 필드 접근 가능
System.out.println(localVar); // 메서드 로컬 변수 접근 가능
}
}
LocalClass localClass = new LocalClass();
localClass.printFields();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
<결과>
Outer Field
Local Variable
2. 중첩 인터페이스(Nested Interface)
중첩 인터페이스는 클래스의 멤버로 선언된 인터페이스를 말한다. 인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관께를 맺는 구현 클래스를 만들기 위해서이다.
예를 들어, 버튼을 눌러 전화나 문자를 보내는 Button 클래스를 만든다고 가정하자.
public class Button {
static interface ClickListener {
void click();
}
ClickListener listener;
void setListener(ClickListener listener) {
this.listener = listener;
}
void touch() {
listener.click();
}
}
public class CallListener implements Button.ClickListener {
@Override
public void click() {
System.out.println("전화를 건다.");
}
}
public class MessageListener implements Button.ClickListener {
@Override
public void click() {
System.out.println("문자를 보낸다.");
}
}
Button 안에 중첩 인터페이스인 ClickListener을 선언하고, setter 메소드로 객체의 타입을 받아 필드에 대입한다. touch()가 발생했을 때, 인터페이스를 통해 구현 객체의 메소드를 호출한다.(listener.click())
1개의 버튼으로 2개의 Listener class를 선언하여 두 가지 방법으로 이벤트를 처리할 수 있다.
public class ButtonEx {
public static void main(String[] args) {
Button btn = new Button();
btn.setListener(new CallListener);
btn.touch();
btn.setListener(new MessageListener);
btn.touch();
}
}
전화를 건다.
문자를 보낸다.
이렇게 인터페이스가 Button 클래스와 강하게 연관되어 있을 경우에, 외부에 노출하지 않고 내부에서만 정의가 가능하다. 또한, 인터페이스가 클래스 내부에 정의되어 있기 때문에 클래스를 변경 할 경우 동작 정의도 함께 관리하기 쉽다.
'JAVA' 카테고리의 다른 글
[JAVA] 예외클래스와 예외처리 (2) | 2024.12.08 |
---|---|
[JAVA] Heap 메모리 구조와 Garbage Collection (2) | 2024.12.08 |
[JAVA] 디자인패턴 - 데코레이터 패턴(Decorator Pattern)을 알아보자 (0) | 2024.12.03 |
[JAVA] 디자인패턴 - 전략 패턴(Strategy Pattern)을 알아보자 (0) | 2024.12.02 |
[JAVA] 오버라이딩 & 오버로딩 (Overriding & Overloading) (0) | 2024.12.01 |