Java공부(코딩)

코딩초보의 자바(Java)공부 20일차 { 중첩 클래스 , 내부클래스 1}

동곤일상 2025. 1. 2. 15:52
반응형

 

오늘은 중첩클래스 , 내부클래스에
대해서 알아보겠습니다!!!
중첩 for문이 있듯이
클래스도 중첩으로 만들 수 있어요!



중첩 클래스의 분류

중첩 클래스는 크게는 2가지

작게는 4가지로 나뉩니다.

 

 

 

**중첩 클래스의 선언 위치**

정적 중첩 클래스- > 정적 변수와 같은 위치

내부 클래스 -> 인스턴스 변수와 같은 위치

 지역 클래스 -> 지역 변수와 같은 위치

 

class Outer {
...
//정적 중첩 클래스
static class StaticNested {
...
}
//내부 클래스
class Inner {
...
}
}

정적 중첩클래스 :

정적메서드와 같이 앞에 static 붙음

 

내부클래스 : 

인스턴스변수(필드)와 같이

static이 앞에 붙지 않음

 


 

class Outer {
public void process() {
//지역 변수
int lcoalVar = 0;
//지역 클래스
class Local {...}
Local local = new Local();
}}

지역 클래스 : 

지역변수와 같이 코드블럭 안에서 생성

 


중첩과 내부는 뭐가 다른거지??

중첩(Nested)**: 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계

(사실상 내가 아님)

 

내부(Inner)**: 나의 내부에 있는 나를 구성하는 요소

 

정적 중첩 클래스**

`static` 붙는다.

바깥 클래스의 인스턴스에 소속되지 않는다.

 

**내부 클래스**

`static` 붙지 않는다.

바깥 클래스의 인스턴스에 소속된다.

 

중첩 클래스를 사용하는 이유

**논리적 그룹화**:

특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리

적으로 그룹화 된다.

패키지를 열었을 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는장점도 있다.

 

**캡슐화**:

중첩 클래스는 바깥 클래스의 `private` 멤버에 접근할 있다.

이렇게 해서 둘을 긴밀하게 연결하고 불필요한 `public` 메서드를 제거할 있다.

부분은 말로 이해하기는 어렵기 때문에 이후에 예제를 통해서 알아보자.

 


정적 중첩 클래스

public class NestedOuter {

 

private int outInstanceValue = 1;

private static int outClassValue = 2;

 

static class Nested{

private int nestedInstanceValue=3;

 

public void print() {

System.out.println(nestedInstanceValue);//자신의 변수에접근

 

//바깥 클래스의 인스턴스 멤버에는 접근 X

// System.out.println(outInstanceValue);

 

//바깥클래스의 클래스멤버(정적변수)에는 접근 가능. private도 접근가능

System.out.println(NestedOuter.outClassValue);

 

//그냥 변수명으로도 접근 가능

System.out.println(outClassValue);}}}

 

 

**private 접근 제어자**

`private` 접근 제어자는 같은 클래스 안에 있을 때만 접근할 있다.

중첩 클래스도 바깥 클래스와 같은 클래스 안에 있다

. 따라서 중첩 클래스는 바깥 클래스의 `private` 접근 제어자에 접근할 있다

 

정적 중첩클래스의 생성

public class NestedOuterMain {

 

public static void main(String[] args) {

NestedOuter nestedOuter = new NestedOuter();

Nested nested = new NestedOuter.Nested();

nested.print();

}}

 

new 바깥클래스.정적중첩클래스() 로 생성

 

 

 

그냥 둘은 다른 클래스라고 생각하는게 편하다

하지만 private를 접근할 수 있는??

 


정적 중첩클래스 활용

일단 다음 코드들을 보자

package nested.nested.ex1;
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}}

 

package nested.nested.ex1;
// Network 객체 안에서만 사용
public class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
public void print() {
System.out.println(content);
}}}
package nested.nested.ex1;
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("hello java");
}
}

 

메인코드를 보면 제어를 담당하는

NetworkMessage 클래스가 전혀보이지

않음을 알수있음

그냥 복잡하게 나누지말고

정적중첩으로 만들어보자 !

 

변경 후

public class NetWork {

 

public void sendMessage(String a ) {

NetworkMessage networkMessage = new NetworkMessage(a);

networkMessage.print();

}

static class NetworkMessage{

private String me;

 

private NetworkMessage(String me) {

this.me = me;

}

public void print() {

System.out.println(me);}}}

 

public class NetworkMain {

 

public static void main(String[] args) {

NetWork network = new NetWork();

network.sendMessage("HI DONGGON");

}}

 

여기서 포인트가 있다면

정적중첩클래스를

private로 정의해

바깥클래스말고는 접근을 할 수 없게끔

설정해놨습니다!!!

 


 

중첩클래스의 접근

 

위 코드처럼

나의 클래스에 포함된 중첩클래스

생성할때는 new 중첩클래스() 로 접근

(바깥클래스 안적어도 됨)

 

하지만 다른곳에 있는 중첩클래스

접근할 때는

new 바깥클래스.중첩클래스() ;

 

주의점

자신이 소속된 클래스(바깥클래스)

외에 다른곳에서 사용이 되고 있다면

중첩클래스의 의미가 없다고 보면 됨


내부클래스

정적 중첩클래스는

바깥클래스와는 별개라고 말씀드렸는데

내부클래스는  바깥클래스의

인스턴스에 소속됨

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(InnerOuter.outClassValue);

}}}

 

public class InnerOuterMain {

 

public static void main(String[] args) {

InnerOuter innerOuter = new InnerOuter();

Inner inner = innerOuter.new Inner();

// Inner inner2 = new Inner(); 불가능ㅇ

inner.print();}}

 

내부클래스의 생성 : 

 

바깥클래스의인스턴스 참조.new 내부클래스();

 

바깥 인스턴스참조를 이용해 Inner인스턴스를 생성하므로

내부인스턴스는 바깥인스턴스를 알고있ㄷ다!

 

 

 


내부클래스 활용

 

내부클래스로 리팩토링 전

package 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() + "의 엔진을 구동합니다.");
}
}

엔진은 `Car` 클래스에서만 사용된다.

엔진을 시작하기 위해서는 차의 충전 레벨과 차량의 이름이 필요하다.

`Car` 인스턴스의 참조를 생성자에서 보관한다.

엔진은 충전 레벨을 확인하기 위해 `Car.getChargeLevel()` 필요하다.

엔진은 차량의 이름을 확인하기 위해 `Car.getModel()` 필요하다.

package 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.inner.ex1;
public class CarMain {
public static void main(String[] args) {
	Car myCar = new Car("Model Y", 100);
	myCar.start();
}}

 

 

리팩토링 후

public class Car {

private String model;

private int chargeLevel;

private CarEngine engine;

 

public Car(String model,int chargeLevel) {

this.model = model;

this.chargeLevel = chargeLevel;

this.engine = new CarEngine();

}

public void start() {

engine.start();

System.out.println(model+"시작 완료");

}

 

private class CarEngine{ //내부클래스

private void start() {

System.out.println("차량 충전 레벨 :"+chargeLevel);

System.out.println(model+"의 엔진 구동!");

}}}

 

내부클래스로 변경해서 바뀐 점

`Car` 인스턴스 변수인 `chargeLevel` 직접 접근할 있다.

`Car` 인스턴스 변수인 `model` 직접 접근할 있다.

 

주의점

중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별

경우에만 사용해야 한다. 외부 여러곳에서 특정 클래스를 사용한다면 중첩(내부) 클래스로 사용하면 안된다.


 

같은 이름의 바깥 변수 접근

public class Shadowing {

private String value ="바깥클래스 필드";

 

class Inner{

private String value = "내부클래스 필드";

 

public void print() {

//지역변수는 접근제어자 쓰면 안됨

String value ="내부클래스의 지역변수";

System.out.println("value : "+value);

System.out.println("this.value : "+this.value);

System.out.println("ShadowingMain.value : "+Shadowing.this.value);

}}}

 

여러분들도 결과를 한 번 예상해봐요!!

public class ShadowingMain {

 

public static void main(String[] args) {

Shadowing s = new Shadowing();

Inner inner = s.new Inner();

inner.print();

}}

value : 내부클래스의 지역변수

this.value : 내부클래스 필드

ShadowingMain.value : 바깥클래스 필드

 

프로그래밍에서 우선순위는 블록 내 (1순위)

즉 가까운변수를 먼저 찾는단 뜻

 

메서드 `print()` 경우 지역 변수인 `value` 가장 가깝다. 따라서 우선순위가 가장 높다.

이렇게 다른 변수들을 가려서 보이지 않게 하는 것을 섀도잉(Shadowing) 한다.

 

다른 변수를 가리더라도 인스턴스의 참조를 사용하면 외부 변수에 접근할 있다.

`this.value` 내부 클래스의 인스턴스에 접근하고, `바깥클래스이름.this` 바깥 클래스의 인스턴스에 접근할

있다.