중첩 클래스
class Outer {
...
//중첩 클래스
class Nested {
...
}
}
중첩 클래스 분류
정적 중첩 클래스는 바깥 클래스의 안에 있지만 바깥 클래스와 관계 없는 전혀 다른 클래스를 말한다. 러시아의 전통 인형 마트료시카를 생각하면 쉽게 이해할 수 있다. 내부 클래스는 바깥 클래스를 구성하는 하나의 요소이다.
정적 중첩 클래스는 바깥 클래스의 인스턴스에 소속되지 않으나 내부 클래스의 경우 바깥 클래스의 인스턴스에 소속되어있다.
static
이 붙는다.static
이 붙지 않는다.❗중첩 클래스 사용처
내부 클래스를 포함한 모든 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나 둘이 아주 긴밀하게 연결되는 특별한 경우에만 사용해야한다. 외부 여러 클래스가 특정 중첩 클래스를 사용한다면 중첩 클래스로 만들면 안 된다.
❗중첩 클래스 사용하는 이유
논리적 그룹화 : 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함되는 것이 논리적으로 더 그룹화가 된다. 다른 곳에서 사용할 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
캡슐화 : 중첩 클래스는 바깥 클래스의private
멤버에 접근할 수 있다. 둘을 긴밀하게 연결하고 불필요한public
메서드를 제거할 수 있다.
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 = " + nestedInstanceValue);
System.out.println("outClassValue = " + outClassValue);
// System.out.println("outInstanceValue = " + outInstanceValue);
}
}
}
static
이 붙는다.public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter nestedOuter = new NestedOuter(); // 바깥 클래스 생성
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
System.out.println("nested = " + nested.getClass());
}
}
public class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println("content = " + content);
}
}
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
}
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("hello java?");
}
}
❗코드
Main을 보면
Network
를 생성하고network.sendMessage()
를 통해 메시지를 전달한다.
NetworkMain
은Network
클래스만을 사용한다.NetworkMessage
클래스는 전혀 사용하지 않는다.NetworkMessage
는 오직Network
내부에서만 사용된다.
✔️정적 중첩 클래스를 활용한 리팩토링
public class NetworkRef {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
// 정적 중첩 클래스의 접근 제어자를 private으로 설정 → 외부에서 별도로 생성 불가능함
private static class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
private void print() {
System.out.println("content = " + content);
}
}
}
정적 중첩 클래스는 바깥 클래스와 관계가 없다. 하지만 내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 된다.
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 = " + innerInstanceValue);
System.out.println("outInstanceValue = " + outInstanceValue);
System.out.println("InnerOuter.outClassValue = " + InnerOuter.outClassValue);
}
}
}
public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter outer = new InnerOuter();
InnerOuter.Inner inner = outer.new Inner();
inner.print();
}
}
❗이해
개념상 바깥 클래스의 인스턴스 내부에 내부 클래스의 인스턴스가 생성된다. 따라서 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스 멤버에 접근이 가능하다.실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아니지만 개념상 인스턴스 안에 생성된다고 이해하면 된다. 실제로는 내부 인스턴스는 바깥 인스턴스의 참조값을 보관한다. 이 참조를 통해 바깥의 인스턴스 멤버에 접근이 가능한 것이다.
public class Car {
private String model;
private int chargeLevel;
private Engine engine;
public Car(String model, int chargeLevel) {
this.model = model;
this.chargeLevel = chargeLevel;
}
public String getModel() {
return model;
}
public int getChargeLevel() {
return chargeLevel;
}
public void start() {
engine.start();
System.out.println(model + " 시작 완료");
}
}
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() + "의 엔진을 구동합니다.");
}
}
✔️내부 클래스를 활용한 리팩토링
public class CarRef {
private String model;
private int chargeLevel;
private Engine engine;
public CarRef(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() {
// 내부 인스턴스에 바깥 인스턴스의 참조값을 보관하므로 바깥 인스턴스의 멤버에 접근할 수 있음 → 따라서 getxxx가 없어도 됨
System.out.println("충전 레벨 확인 : " + chargeLevel);
System.out.println(model + "의 엔진을 구동합니다.");
}
}
}
public class CarRefMain {
public static void main(String[] args) {
CarRef carRef = new CarRef("Model Y", 100);
carRef.start();
}
}
바깥 클래스의 인스턴스 변수와 내부 클래스의 인스턴스 변수 이름이 같은 경우
public class ShadowingMain {
private int value = 1;
class Inner {
private int value = 2;
void go() {
int value = 3;
System.out.println("value = " + value); // 지역변수
System.out.println("Inner.value = " + this.value); // 내부 클래스 멤버
System.out.println("ShadowingMain.value = " + ShadowingMain.this.value); // 바깥 클래스 멤버
}
}
public static void main(String[] args) {
ShadowingMain shadowingMain = new ShadowingMain();
Inner inner = shadowingMain.new Inner();
inner.go();
}
}