오늘의 잔디
내일부터 다시 열심히 해보자
아자아자 파이팅이야
---
간단한 예제를 리팩토링 하면서 내부 클래스를 어떻게 활용하는지 알아보자.
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 는 바깥 클래스의 인스턴스에 접근할 수 있다.
프로그래밍에서 가장 중요한 것은 명확성이다. 이렇게 이름이 같은 경우 처음부터 이름을 서로 다르게 지어서 명확하게
구분하는 것이 더 나은 방법이다.