코딩초보의 자바(Java)공부 20일차 { 중첩 클래스 , 내부클래스 1}
오늘은 중첩클래스 , 내부클래스에
대해서 알아보겠습니다!!!
중첩 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` 는 바깥 클래스의 인스턴스에 접근할 수
있다.