Java 공부 54일차(중첩 클래스, 내부 클래스란?)3편

임선구·2025년 7월 23일

몸 비틀며 Java

목록 보기
55/58

오늘의 잔디

내일부터 다시 열심히 해보자

아자아자 파이팅이야

---

내부 클래스의 활용

간단한 예제를 리팩토링 하면서 내부 클래스를 어떻게 활용하는지 알아보자.

내부 클래스로 리팩토링 전

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 + " 시작 완료");
 }
}
  • Car 클래스는 엔진에 필요한 메서드들을 제공해야 한다. 다음 메서드는 엔진에서만 사용하고, 다른 곳에서는 사
    용하지 않는다.
    • getModel()
    • getChargeLevel()
  • 결과적으로 Car 클래스는 엔진에서만 사용하는 기능을 위해 메서드를 추가해서, 모델 이름과 충전 레벨을 외부
    에 노출해야 한다.
package nested.inner.ex1;
public class CarMain {
 public static void main(String[] args) {
 Car myCar = new Car("Model Y", 100);
 myCar.start();
 }
}

실행 결과

충전 레벨 확인: 100
Model Y의 엔진을 구동합니다.
Model Y 시작 완료

내부 클래스로 리팩토링 후

엔진은 차의 내부에서만 사용된다. 엔진을 차의 내부 클래스로 만들어보자. 또한 엔진은 차의 충전 레벨과 모델 명에 접근해야 한다.

package 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 {
 public void start() {
 System.out.println("충전 레벨 확인: " + chargeLevel);
 System.out.println(model + "의 엔진을 구동합니다.");
 }
 }
}
  • 엔진을 내부 클래스로 만들었다.
  • Engine.start() 를 기존과 비교해보자.
    • Car 의 인스턴스 변수인 chargeLevel 에 직접 접근할 수 있다.
    • Car 의 인스턴스 변수인 model 에 직접 접근할 수 있다.

내부 클래스의 생성

  • 바깥 클래스에서 내부 클래스의 인스턴스를 생성할 때는 바깥 클래스 이름을 생략할 수 있다.
    • 예) new Engine()
  • 바깥 클래스에서 내부 클래스의 인스턴스를 생성할 때 내부 클래스의 인스턴스는 자신을 생성한 바깥 클래스의 인
    스턴스를 자동으로 참조한다. 여기서 new Engine() 로 생성된 Engine 인스턴스는 자신을 생성한 바깥의
    Car 인스턴스를 자동으로 참조한다.
package nested.inner.ex2;
public class CarMain {
 public static void main(String[] args) {
 Car myCar = new Car("Model Y", 100);
 myCar.start();
 }
}

실행 결과

충전 레벨 확인: 100
Model Y의 엔진을 구동합니다.
Model Y 시작 완료

리팩토링 전의 문제

  • Car 클래스는 엔진에 필요한 메서드들을 제공해야 한다. 다음 메서드는 엔진에서만 사용하고, 다른 곳에서는 사
    용하지 않는다.
    • getModel()
    • getChargeLevel()
  • 결과적으로 엔진에서만 사용하는 기능을 위해 메서드를 추가해서, 모델 이름과 충전 레벨을 외부에 노출해야 한다.

리팩토링 전에는 결과적으로 모델 이름과 충전 레벨을 외부에 노출했다. 이것은 불필요한 Car 클래스의 정보들이 추가로 외부에 노출되는 것이기 때문에 캡슐화를 떨어뜨린다.
리팩토링 후에는 getModel() , getChargeLevel() 과 같은 메서드를 모두 제거했다. 결과적으로 꼭 필요한 메서드만 외부에 노출함으로써 Car 의 캡슐화를 더 높일 수 있었다.

이제 처음에 설명한 중첩 클래스를 언제 사용해야 하는지 설명한 내용을 다시 정리해보자.

중첩 클래스는 언제 사용해야 하나?

  • 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별
    한 경우에만 사용해야 한다. 외부 여러곳에서 특정 클래스를 사용한다면 중첩 클래스로 사용하면 안된다.

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

  • 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화가 된다. 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
  • 캡슐화: 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다.

같은 이름의 바깥 변수 접근

바깥 클래스의 인스턴스 변수 이름과 내부 클래스의 인스턴스 변수 이름이 같으면 어떻게 될까?
다음 예제 코드를 보자.

package nested;
public class ShadowingMain {
 public 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
this.value = 2
ShadowingMain.value = 1

변수의 이름이 같기 때문에 어떤 변수를 먼저 사용할지 우선순위가 필요하다.
프로그래밍에서 우선순위는 대부분 더 가깝꺼나, 더 구체적인 것이 우선권을 가진다. 쉽게 이야기해서 사람이 직관적으로 이해하기 쉬운 방향으로 우선순위를 설계한다.

메서드 go() 의 경우 지역 변수인 value 가 가장 가깝다. 따라서 우선순위가 가장 높다.
이렇게 다른 변수들을 가려서 보이지 않게 하는 것을 섀도잉(Shadowing)이라 한다.

다른 변수를 가리더라도 인스턴스의 참조를 사용하면 외부 변수에 접근할 수 있다.
this.value 는 내부 클래스의 인스턴스에 접근하고, 바깥클래스이름.this 는 바깥 클래스의 인스턴스에 접근할 수 있다.

프로그래밍에서 가장 중요한 것은 명확성이다. 이렇게 이름이 같은 경우 처음부터 이름을 서로 다르게 지어서 명확하게
구분하는 것이 더 나은 방법이다.

profile
끝까지 가면 내가 다 이겨

0개의 댓글