클래스안의 클래스를 말한다. 외부 클래스의 인스턴스에 연결되어 존재하는 내부 클래스, 외부 클래스의 내부에 위치 할 뿐 인스턴스에 연결되어있지 않고 독립적인 정적 중첩 클래스 라고 한다.
(핵심은 외부 인스턴스와의 관계이다.)
우리가 외부 클래스를 호출해서 사용할 때, 하나의 클래스에서만 사용을 하는 클래스가 생길 수 있다. 독립된 클래스를 사용하면 코드가 더 길어질 뿐 아니라 외부에 노출할 필요가 없는 정보를 노출하게된다.
중첩 클래스는 이런 불편함을 직관적으로 정리할 수 있다.
먼저 static 을 확실하게 짚고 넘어가자.
: 클래스에 속하며 클래스가 로딩 될 때 단 한번만 메서드 영역에 생성된다. 자바가 인스턴스로 호출 가능하게 문법적 허용을 해주지만, 내부에서는 클래스 이름으로 변환해서 호출한다. (인스턴스에 속성처럼 하나하나 생성되는 게 아니다.)
⭐️ 정적 중첩 클래스는 외부 클래스의 인스턴스와는 전혀 연결되지 않는다. 따라서 외부 클래스 객체 없이도 독립적으로 생성할 수 있다. 다만 “외부 클래스와 밀접한 관련이 있다”는 점을 코드 구조상 드러내기 위해 내부에 두는 경우가 많다.
package nested.nested.ex2;
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
// ➡️ 바깥 클래스에서 정적 중첩 클래스의 객체를 생성해 메서드를 호출
}
private static class NetworkMessage { // 📌 정적 중첩 클래스
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
}
➡️ 독립된 클래스를 사용하는 방식과 같다. 단지 안에 클래스가 위치할 뿐이다.
package nested.nested.ex2;
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("hello java");
}
}
➡️ 바깥 클래스의 메서드를 통해서 정적 중첩 클래스의 객체를 생성한다.
내부 클래스는 바깥 클래스의 속성이 된다. ➡️ 필드 & 메서드
따라서 외부 클래스가 생성될 때 내부 클래스 인스턴스를 같이 만들 수도 있고, 필요할 때 new를 통해 추가로 만들 수도 있다.
내부 클래스는 외부 클래스의 private 필드/메서드에 접근할 수 있지만, 외부 클래스는 내부 클래스의 private 멤버에 직접 접근할 수 없다‼️
package nested.nested.inner.ex2;
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 { // Car 에서만 사용하는 클래스
public void start() { // ‼️ 외부 클래스는 내부 클래스의 private 멤버에 접근 할 수 없다.
System.out.println("충전 레벨 확인 : " + chargeLevel);
System.out.println(model+" 의 엔진을 구동합니다.");
}
}
}
➡️ 필드에 내부 클래스 타입이 선언됐다. 바깥 클래스 생성자 안에 new 내부 클래스() 로 객체를 생성해서 본인의 필드에 넣었다.
🗣️ : 바깥 클래스가 내부 클래스의 참조값을 알고 있다. 하지만
private에 접근할 수 없다. 내부 클래스는 외부 클래스의private에 접근 가능한데. 왜 그럴까?
우선, 일반적으로도 다른 클래스라면 참조값을 가지고 있더라도 private에는 접근 할 수 없다. 그래서 내부 클래스가 바깥 클래스의 private 필드에 접근할 수 있는 이유는 “참조값이 있어서”가 아니다.
👉 자바 언어 차원에서 컴파일러가 내부 클래스를 바깥 클래스의 멤버로 취급해 접근할 수 있게 특별히 허용하기 때문
package nested.nested.inner.ex2;
public class CarMain {
public static void main(String[] args) {
Car car = new Car("Model Y", 100);
car.start();
}
}
➡️ main에서 외부 클래스를 생성해 메서드를 실행했다. 외부클래스에서 내부클래스를 생성해서 메인에서 호출한 메서드를 실행하는데 사용한다.
외부 클래스 내부에서 만들 때
→ new 내부클래스() : 이미 외부 클래스 안이니까 어느 외부 객체에 속하는지 자동으로 알 수 있다.
외부 클래스 외부 (main 등)
→ 외부클래스.내부클래스 변수 = 외부인스턴스.new 내부클래스() : 내부 클래스는 외부 인스턴스에 속해 있으므로, 어느 외부 객체의 내부 클래스인지 알려줘야한다. 그래서 외부 인스턴스를 앞에 꼭 붙여야 한다.
정적 중첩 클래스
→ new 외부클래스.정적중첩클래스() : 외부 객체랑 관계가 없으니까 바로 위치(소속) 만 알려주면 된다.
package nested;
public class ShadowingMain {
private int value = 1;
class Inner {
public int value = 2;
void go() {
int value = 3;
System.out.println("value = " + value);
System.out.println("this.value = " + this.value);
System.out.println("ShadowingMain.value = "+ShadowingMain.this.value);
}
}
public static void main(String[] args) {
ShadowingMain main = new ShadowingMain();
Inner inner = main.new Inner();
inner.go();
}
}
value 값이 총 3개가 있다.
자바는 항상 본인과 가장 가까운 지역변수를 먼저 확인한다. 그리고 본인이 속한 인스턴스의 멤버변수, 마지막은 외부 클래스의 본인 멤버변수로 인식한다.
하나의 value 값을 찾으면 그 값이 아닌 같은 이름을 가진 변수들은 가려진다. 이걸 섀도잉이라고 한다.