중첩 클래스, 내부 클래스란?
for문 안에 for문을 중첩하는 것을 중첩(Nested) for문이라 한다.
클래스 안에 클래스를 중첩해서 정의한 것을 중첩 클래스(Nested Class)라 한다.
중첩 클래스의 분류
- 정적 중첩 클래스
- 내부 클래스 종류
- 내부 클래스(inner class): 바깥 클래스의 인스턴스의 멤버에 접근
- 지역 클래스(local class): 내부 클래스의 특징 + 지역 변수에 접근
- 익명 클래스(anonymous class): 지역 클래스 특징 + 클래스의 이름이 없는 특별한 클래
- 중첩(Nested): 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계
- 내부(Inner): 나의 내부에서 나를 구성하는 요소
정리) 내부 클래스들은 바깥 클래스의 인스턴스에 소속된다. 정적 중첩 클래스는 그렇지 않다.
중첩 클래스를 사용하는 이유
- 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우.
- 캡슐화: 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 없다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다.
정적 중첩 클래스
package nested.nested;
public class NestedOuter {
private static int outClassValue = 3;
private int outIntanceValue =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);
}
}
}
-------------------------------------------------------
package nested.nested;
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
System.out.println("nestedClass = " + nested.getClass());
}
}
- 정적 중첩 클래스는 new 바깥클래스.중첩클래스()로 생성할 수 있다.
- 중첩 클래스는 NestedOuter.Nested와 같이 바깥클래스.중첩 클래스로 접근할 수 있다.
- 여기서 new NestedOuter()로 만든 바깥 클래스의 인스턴스와 new NestedOuter.Nested()로 만든 정적 중첩 클래스의 인스턴스는 서로 아무 상관 관계가 없는 인스턴스이다. 단지 클래스 구조상 중첩해 두었을 뿐이다.
- 참고로 둘이 아무런 관련이 없으므로 정적 중첩 클래스의 인스턴만 따로 생성해도 된다.
정리
정적 중첩 클래스는 사실 다른 클래스를 그냥 중첩해 둔것일 뿐이다. 아무런 관계가 없다
class NestedOuter(){
}
class Nested(){
}
위 코드와 차이는 단지 같은 클래스에 있으니 private에 접근할 수 있다는 정도이다.
정적 중첩 클래스의 활용
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 world");
}
}
Network 관련 라이브러리를 사용하기 위해서 ex2 패키지를 열어본 개발자는 해당 클래스만 확인할 것이다. 추가로 NetworkMessage가 중첩 클래스에 rpviate 접근 제어자로 되어 있는 것을 보고, Network 내부에서만 단독으로 사용하는 클래스라고 바로 인지할 수 있다.
중첩 클래스의 접근
나의 클래스에 포함된 중첩 클래스가 아니라 다른 곳에 있는 중첩 클래스에 접근할때는 바깥클래스.중첩클래스로 접근해야한다.
중첩 클래스는 자신이 소속된 바깥 클래스 안에서 사용되는 것이다. 따라서 그 외 외부에서 생성하고 사용하고 있다면 이미 중첩 클래스의 용도에 맞지 않을 수 있다. 이때는 중첩 클래스를 밖으로 빼는 것이 나은 선택이다.
내부 클래스
정적 중첩 클래스는 바깥 클래스오 서로 관계가 없다. 하지만 내부 클래스는 바깥 클래스의 인스턴스를 이루느 ㄴ요소가 된다. 쉽게 이야기해서 내부 클래스는 바깥 클래스의 인스턴스에 소속된다.
정적 중첩 클래스
- static이 붙는다
- 바깥 클래스의 인스턴스에 소속되지 않는다.
내부 클래스
- static이 붙지 않는다
- 바깥 클래스의 인스턴스에 소속된다.
package nested.nested.inner;
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);
}
}
}
- 내부 클래스는 앞에 static이 붙지 않는다. 쉽게 이야기해서 인스턴스 멤버가 된다.
- 내부 클래스는
- 자신의 멤버에는 당연히 접근할 수 있다
- 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.
- 바깥 클래스의 클래스 멤버에 접근할 수 있다.
- 바깥 클래스의 private 접근 제어자에 접근할 수 있다. (내부에 있으니까 당연)
package nested.nested.inner;
public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter innerOuter = new InnerOuter();
InnerOuter.Inner inner = innerOuter.new Inner();
inner.print();
System.out.println(inner.getClass());
}
}
- 내부 클래스는 바깥 클래스의 인스턴스에 소속된다. 따라서 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.
- 내부 클래스는 new 바깥클래스의 인스턴스 참조 내부클래스()로 생성할 수 있다.
- 내부 클래스는 바깥 클래스의 인스턴스에 소속되어야 한다. 따라서 내부 클래스를 생성할 때, 바깥 클래스의 인스턴스 참조가 필요하다.
- outer.new Inner()에서 outer는 바깥 클래스의 인스턴스 참조를 가진다.
- outer.new Inner()로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성된다.
- 따라서 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.
실행 결과
1
2
3
class nested.nested.inner.InnerOuter$Inner
내부 클래스 생성 - 개념
- 개념상 바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.
- 따라서 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스의 멤버에 접근할 수 있다
내부 클래스 생성 - 실제
- 실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아니다. 하지만 개념상 인스턴스 안에 생성된다고 이 해하면 충분하다.
- 실제로는 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있다
내부 클래스의 활용
리팩토링전
package nested.nested.inner.ex1;
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 + " 시작 완료");
}
}
--------------------------------------------------
package nested.nested.inner.ex1;
//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() + "의 엔진을 구동합니다.");
}
}
---------------------------------------------------
package nested.nested.inner.ex1;
public class CarMain {
public static void main(String[] args) {
Car myCar = new Car("Model Y", 100);
myCar.start();
}
}
리팩토링 후
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 {
private void start() {
System.out.println("출전 레벨 확인: " + chargeLevel);
System.out.println(model + "의 엔진을 구동합니다.");
}
}
}
리팩토링 후에는 getModel(), getChargeLevel()과 같은 엔진에서만 사용하는 메서드를 제거하여 꼭 필요한 메서드만 외부에 노출함으로써 Car의 캡슐화를 높힘.
중첩 클래스는 언제 사용해야 하나?
- 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 두링 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용해야한다. 외부 여러곳에서 특정 클래스를 사용한다면 중첩 클래스로 사용하면 안된다.
중첩 클래스를 사용하는 이유
- 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화가 된다. 패키지를 열었을 떄 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
- 캡슐화: 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연겨라혹 불필요한 public 메서드를 제거할 수 있다.
같은 이름의 바깥 변수 접근
package nested.nested;
public class ShadowingMain {
public int value = 1;
class Innenr {
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();
main.new Innenr().go();
}
}
실행 결과
value = 3
this.value = 2
ShadowingMain.value = 1
변수의 이름이 같기 때문에 어떤 변수를 먼저 사용할지 우선순위가 필요하다.
프로그래밍에서 우선순위는 대부분 더 가깝거나, 더 구체적인 것이 우선권을 가진다.
메서드 go()의 경우 지역 변수인 value 가 가장 가깝다. 따라서 우선순위가 가장 높다.
이렇게 다른 변수들을 가려서 보이지 않게 하는 것을 섀도잉(shadowing)이라 한다.
출처 - 김영한의 실전 자바 중급편 1
'Backend > Java' 카테고리의 다른 글
예외 처리 1 (0) | 2024.08.13 |
---|---|
중첩 클래스, 내부 클래스 - 2 (0) | 2024.08.13 |
날짜와 시간 (0) | 2024.08.10 |
열거형 - ENUM (0) | 2024.08.09 |
Class, System 클래스 (0) | 2024.08.08 |