2025. 4. 7. 17:40ㆍJava
◎ 중첩 클래스
-> 클래스 안에 클래스를 중첩해서 정의할 수 있는데 이를 중첩 클래스라 한다.
class Outer{
// 중첩 클래스
class Nested{
...
}
}
-> 중첩 클래스는 클래스를 정의하는 위치에 따라 아래와 같이 분류된다.
-> 정적 중첩 클래스 : 정적 변수와 같은 위치의 클래스
-> 내부 클래스 : 인스턴스 변수와 같은 위치의 클래스
-> 지역 클래스 : 지역 변수와 같은 위치의 클래스
◎ 중첩 클래스의 용도
-> 특정 클래스가 다른 하나의 클래스 내부에서만 사용되거나 둘이 긴밀하게 연결되어 있는 경우에만 사용한다. 외부의 여러 클래스가 특정 중첩 클래스를 사용한다면 중첩 클래스로 만들면 안된다.
-> 논리적 그룹화 : 특정 클래스가 다른 하나의 클래스에서만 사용되는 경우 해당 클래스 내에 포함하는 것이 논리적으로 더 그룹화가 된다.
-> 캡슐화 : 중첩 클래스는 외부 클래스의 private 멤버에 접근 가능하다. 이 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다.
◎ 정적 중첩 클래스 VS 내부 클래스
-> 중첩(Nested) : 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계
-> 내부(Inner) : 나의 내부에 있는 나를 구성하는 요소
-> 정적 중첩 클래스는 외부 클래스의 안에 있지만 외부 클래스와는 관계가 없는 전혀 다른 클래스다. static이 붙는다.
-> 내부 클래스는 외부 클래스의 있으면서 외부 클래스를 구성하는 요소 클래스이다.
◎ 내부 클래스의 종류
-> 내부 클래스(inner class) : 외부 클래스의 인스턴스의 멤버에 접근
-> 지역 클래스(local class) : 내부 클래스의 특징 + 지역 변수에 접근
-> 익명 클래스(anonymous class) : 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스
◎ 중첩 클래스
public class NestedOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
// 정적 중첩 클래스
static class Nested {
private int nestedInstanceValue = 1;
public void print() {
// 자신의 멤버에 접근
System.out.println(nestedInstanceValue);
// 외부 클래스의 인스턴스 멤버에는 점근할 수 없다.
// System.out.println(outInstanceValue);
// 외부 클래스의 클래스 멤버에는 접근할 수 있다.(private도 접근 가능)
System.out.println(NestedOuter.outClassValue);
}
}
}
-> 정적 중첩 클래스는 클래스 앞에 static이 붙는다.
-> 정적 중첩 클래스는 자신의 멤버와 외부 클래스의 클래스 멤버(static)에 접근할 수 있으며 외부 클래스의 인스턴스 멤버에는 접근할 수 없다.
-> 중첩 클래스는 외부 클래스와 같은 클래스 안에 있기 때문에 private 접근 제어자에도 접근할 수 있다.
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
System.out.println("nested.getClass() = " + nested.getClass());
}
}
-> 결과
-> 정적 중첩 클래스는 new 외부클래스.중첩클래스()로 생성한다.
-> new NestedOuter()로 만든 외부 클래스의 인스턴스와 new NestedOuter.Nested()로 만든 정적 중첩 클래스의 인스턴스는 서로 아무 관계가 없는 인스턴스이다. 단지 클래스 구조 상 중첩된 것이다.
-> 중첩 클래스.getClass()를 출력하면 class nested.nested.NestedOuter$Nested가 출력되는데 외부클래스$중첩 클래스의 조합으로 출력이 된 것이다.
-> 다음은 위 코드의 구조를 그림으로 나타낸 것이다.
-> 정적 중첩 클래스는 외부 클래스의 정적 필드에는 접근할 수 있다.(정적 필드에 접근할 때 인스턴스 참조가 필요 없고, static은 메서드 영역에서 공통으로 관리되기 때문) 하지만 외부 클래스가 만든 인스턴스 필드에는 외부 인스턴스의 참조가 없기 때문에 접근할 수 없다.
◎ 정적 중첩 클래스의 활용
-> 정적 중첩 클래스를 활용하는 방법에 대해 알아본다. 먼저 아래 코드는 정적 중첩 클래스를 사용하지 않았을 때의 코드이다.
// Network 객체 내에서만 사용
public class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
}
-> Network는 text를 입력받아 NetworkMessage를 생성해서 메시지를 출력한다.
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("message!!");
}
}
-> 결과
-> NetworkMessage는 외부에서 사용되지 않고 Network에서만 사용된다. 외부의 호출 없이 단순히 Network에서만 사용되는데 굳이 클래스 파일을 두 개로 분리해서 만들면 코드를 이해하는데 시간도 걸리고 NetworkMessage가 Network에서만 사용하려는 의미도 파악하기 쉽지 않다.
-> 다음은 위 코드를 정적 중첩 클래스로 리팩토링한 코드이다.
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
// NetworkMessage를 정적 중첩 클래스로 선언
private static class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
}
-> Network 내에서만 사용되는 NetworkMessage 클래스를 private으로 정적 중첩 클래스로 만들었다. 이렇게 하면 NetworkMessage는 외부에서 접근할 수 없고 Network에서만 사용할 수 있게되고, 하나의 클래스 파일에 NetworkMessage의 의도를 더욱 쉽게 파악할 수 있다.
-> 중첩 클래스(내부 클래스 포함)의 용도는 자신이 소속된 외부 클래스 안에서 사용되는 것이다. 만약 자신이 소속된 클래스 뿐만 아니라 외부에서도 생성하고 사용한다면 중첩 클래스의 용도에 맞지 않을 수 있다. 이런 경우에는 클래스를 따로 빼서 만드는 것이 더 낫다.
◎ 내부 클래스
-> 외부 클래스의 인스턴스를 이루는 요소가 되는 클래스이다. 외부 클래스의 인스턴스에 소속된다.
-> 다음은 내부 클래스가 사용된 코드이다.
public class InnerOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
// 내부 클래스
class Inner {
private int innerInstanceValue = 1;
public void print() {
// 자신의 멤버에 접근
System.out.println(innerInstanceValue);
// 외부 클래스의 인스턴스 멤버에 접근(private도 접근 가능)
System.out.println(outInstanceValue);
// 외부 클래스 멤버에 접근(private도 접근 가능)
System.out.println(outClassValue);
}
}
}
public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter outer = new InnerOuter();
InnerOuter.Inner inner = outer.new Inner(); // 내부 클래스의 인스턴스 생성
inner.print();
System.out.println("inner.getClass() = " + inner.getClass());
}
}
-> 결과
-> 내부 클래스는 외부 클래스의 인스턴스에 소속된다. 따라서 외부 클래스의 인스턴스 정보가 있어야 내부 클래스를 생성할 수 있다.
-> 내부 클래스는 외부클래스 인스턴스 참조.new 내부 클래스()로 생성한다.
-> 다음은 위 코드의 구조를 나타낸 그림이다.
-> 내부 인스턴스는 외부 인스턴스의 참조를 보관한다. 해당 참조를 가지고 외부 인스턴스의 멤버에 접근할 수 있다.
◎ 내부 클래스 활용
-> 내부 클래스를 활용하는 방법에 대해 알아본다. 먼저 다음은 내부 클래스를 사용하지 않는 코드이다.
// Car에서만 사용
public class Engine {
private Car car;
public Engine(Car car) {
this.car = car;
}
public void start() {
System.out.println("충전 레벨 확인 : " + car.getChargeLevel());
System.out.println(car.getModel() + "의 엔진 구동");
}
}
-> 위 클래스는 Car 클래스에서만 사용되는 클래스다. Engine은 Car 인스턴스의 참조를 생성자에서 보관한다.
public class Car {
private String model;
private int chargeLevel;
private Engine engine;
public Car(String model, int chargeLevel) {
this.model = model;
this.chargeLevel = chargeLevel;
this.engine = new Engine(this);
}
// Engine에서만 사용하는 메서드
public String getModel() {
return model;
}
// Engine에서만 사용하는 메서드
public int getChargeLevel() {
return chargeLevel;
}
public void start() {
engine.start();
System.out.println(model + " 시작 완료");
}
}
-> Car 클래스는 Engine에 필요한 메서드를 제공한다. 또한 해당 메서드들(getModel(), getChargeLevel())은 Engine 클래스 외에는 사용하지 않는다. 이는 특정 클래스에서만 사용될 것을 불필요하게 외부에 노출해야 한다는 단점이 있다.
public class CarMain {
public static void main(String[] args) {
Car car = new Car("Damas", 10);
car.start();
}
}
-> 결과
-> 다음은 위 코드를 내부 클래스를 사용하도록 리팩토링한 코드이다.
public class Car {
private String model;
private int chargeLevel;
private Engine engine;
public Car(String model, int chargeLevel) {
this.model = model;
this.chargeLevel = chargeLevel;
this.engine = new Engine();
}
public void start() {
engine.start();
System.out.println(model + " 시작 완료");
}
private class Engine {
public void start() {
System.out.println("충전 레벨 확인 : " + chargeLevel);
System.out.println(model + " 엔진 구동");
}
}
}
-> Engine 클래스를 Car의 내부 클래스로 만들었다.
-> Engine 클래스는 Car의 내부 클래스이므로 car의 인스턴스 변수인 chargeLevel, model에 직접 접근할 수 있다. 이제 Engine 클래스에서 Car 인스턴스의 참조를 받아와서 car 인스턴스의 메서드를 호출하지 않아도 된다.
-> 외부 클래스에서 내부 클래스의 인스턴스를 생성할 때는 외부 클래스의 이름을 생략할 수 있다. : new Engine();
-> 외부 클래스에서 내부 클래스의 인스턴스를 생성할 때 내부 클래스의 인스턴스는 자신을 생성한 바깥 클래스의 인스턴스를 자동으로 참조한다.
-> 리팩토링 이후 getModel(), getChargeLevel()과 같은 메서드를 모두 제거해서 필요한 메서드만 외부에 노출하여 Car의 캡슐화를 더 높였다.
★ 참고
'Java' 카테고리의 다른 글
열거형 - ENUM (0) | 2025.04.01 |
---|---|
래퍼, Class 클래스 (0) | 2025.03.27 |
String 클래스 (0) | 2024.11.18 |
불변객체 (0) | 2024.10.31 |
Object 클래스 (0) | 2024.10.29 |