[Java] 중첩 클래스, 내부 클래스
클래스안에 클래스를 중첩해서 정의할 수 있는데, 이것을 중첩 클래스라고 한다.
중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류할 수 있다.
- 정적 중첩 클래스
- 내부 클래스 종류
- 내부 클래스
- 지역 클래스
- 익명 클래스
중첩 클래스를 정의하는 위치는 변수의 선언 위치와 같다.
변수의 선언 위치
- 정적 변수(클래스 변수)
- 인스턴스 변수
- 지역 변수
중첩 클래스의 선언 위치
- 정적 중첩 클래스
- 내부 클래스
- 지역 클래스
class Outer {
...
//정적 중첩 클래스
static class StaticNested {
...
}
//내부 클래스
class Inner {
...
}
}
내부 클래스는 앞에 static 없이 class를 사용한다.
정적 충첩은 정적 변수와 같이 앞에 static 을 붙인다.
class Outer {
public void process() {
//지역 변수
int lcoalVar = 0;
//지역 클래스
class Local {
...
}
Local local = new Local();
}
}
지역 클래스는 지역 변수와 같이 코드 블럭 안에서 클래스를 정의한다.
중첩과 내부의 차이는 무엇인가?
중첩은 나의 안에 있지만, 내것이 아닌 것을 말한다.
- 큰 나무 상자안에 전혀 다른 작은 나무 상자를 넣은 것을 중첩이라고 한다.
- 바깥 클래스의 안에 있지만 바깥 클래스와 관계없는 전혀 다른 클래스
내부는 나의 내부에서 나를 구성하는 요소를 말한다.
- 나의 심장은 나의 내부에서 나를 구성하는 요소이다.
- 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소이다.
정리하면 내부 클래스들은 바깥 클래스의 인스턴스에 소속된다. 정적 충첩 클래스는 그렇지 않다.
중첩 클래스는 언제 사용해야 하나?
논리적인 그룹화 : 한 클래스안에서만 해당 메서드를 사용할 경우
캡슐화: 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 불필요한 public 메서드를 제거할 수 있다.
정적 중첩 클래스
package nested;
public class NestedOuter {
public class NestedOuter {
private static int outClassValue = 3;
private int outInstatncValue = 2 ;
static class Nested {
private int nestedInstatnceValue = 3;
public void print() {
System.out.println(nestedInstatnceValue);
//자신의 클래스에 있는 멤버에 접근하는 것은 가능하다.
System.out.println(outClassValue);
//static 변수는 접근 가능, 클래스 멤버에는 접근할 수 있다. private 여도 가능하다.
System.out.println(outInstatncValue);
//바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
}
}
}
}
static은 메서드 영역에 올라가기 때문에 접근할 수 있다.
바깥 클래스의 인스턴스 멤버에 접근하기 위해서는 따로 인스턴스를 생성하여 접근해야한다. 혹은 static으로 변경
public class NestedOuter {
private static int outClassValue = 3;
private int outInstatncValue = 2;
static class Nested {
private int nestedInstatnceValue = 3;
public void print() {
System.out.println(nestedInstatnceValue);
System.out.println(outClassValue); // 가능
// 바깥 클래스의 인스턴스를 생성해야 인스턴스 변수에 접근 가능
NestedOuter outer = new NestedOuter();
System.out.println(outer.outInstatncValue);
}
}
public static void main(String[] args) {
Nested nested = new Nested();
nested.print();
}
}
Main 에서 호출하는 방법
package nested;
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
}
}
클래스안에 클래스를 호출 하는 것이 때문에 .(dot)을 이용하여 호출한다.
정적 중첩 클래스는 사실 다른 클래스를 그냥 넣어둔 것 뿐이다. 이 둘은 아무런 관계가 없다.
다만 같은 클래스에 적었기 때문에 private 에도 접근 할 수 있다.
정적 중첩 클래스의 활용
package nested.ex1;
public class NetworkMes {
private String content;
public NetworkMes(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
package nested.ex1;
public class Network {
public void sendMessage(String text) {
NetworkMes networkMes = new NetworkMes(text);
networkMes.print();
}
}
package nested.ex1;
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("Hello World");
}
}
Hello World가 츨력된다.
1. String text 가 매개변수로 받아서 NetworkMes 의 생성자로 들어가게 된다.
2. content = "Hello World" 로 들어간다.
3. networkMes.print(); 실행
4. System.out.println(content); 실행 → "Hello World" 출력!
근데 이렇게 하면 클래스가 두 개가 된다.
보기 불편하고 헷갈리니,
package nested.ex2;
public class Network {
public void sendmes(String text) {
Mess mess = new Mess(text);
mess.print();
}
private static class Mess {
private String content;
public Mess(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
System.out.println("안녕하세요");
}
}
}
이렇게 한 클래스에 중첩해서 넣으면 좋을 것이다
중첩인 것과 private접근 제어자를 보고 해당 클래스 내부에서만 사용하는 클래스라고 인지할 수 있다.
나의 클래스에 포함된 중첩 클래스가 아니라 다른 곳에 있는 중첩클래스에 접근할 때는 바깥 클래스, 중첩클래스로 접근해야 한다.
ex1.ex2.mess ...
내부클래스
정적 충첩 클래스는 바깝 클래스와 서로 관계가 없다. 하지만 내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 된다.
정적 중첩 클래스
- static이 붙는다
- 바깥 클래스의 인스턴스에 소속되지 않는다
내부 클래스
- static이 붙지 않는다
- 바깥 클래스의 인스턴스에 소속된다
package nested.inner;
public class innerOuter {
private static int outClassvalue = 3;
private int outInstanceValue = 2;
class Inner {
private int innerValue = 1;
public void print() {
//자기 자신접근
System.out.println(innerValue);
//접근 제어자가 private 여도 외부 인스턴스에 접근 가능
System.out.println(outInstanceValue);
//static 가능
System.out.println(outClassvalue);
}
}
}
그럼 Main에서 돌릴 때는,
package nested.inner;
public class InnerMAin {
public static void main(String[] args) {
innerOuter outer = new innerOuter();
innerOuter.Inner inner = outer.new Inner();
inner.print();
}
}
중첩 클래스는 아래와 같은 경우 사용된다.
- 특정 클래스가 다른 하나의 클래스 안에서만 사용될 경우
- 둘이 아주 긴밀하게 연결되어 있는 특별한 경우
- 외부 여러곳에서 특정 클래스를 사용한다면 중첩 클래스로 사용하면 안된다.
- 논리적인 그룹화 : 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화가 된다.
- 캡슐화 : 중첩 클래스는 바깝 클래스의 private 맴버에 접근할 수 있다. 이렇게 할 경우 긴밀하게 연결하고 불필요한 public메서드를 제거할 수 있다.
같은 이름의 바깝 변수 접근
바깥 클래스의 인스턴스 변수 이름과 내부 클래스의 인스턴스 변수 이름이 같으면 어떻게 될까?
package nested.inner.ex1;
public class ShdowingMAin {
public int value = 1;
class Inner {
public int value =2;
void go() {
int value = 3;
System.out.println("value is " + value);
}
}
public static void main(String[] args) {
ShdowingMAin main = new ShdowingMAin();
ShdowingMAin.Inner inner = main.new Inner();
inner.go();
}
}
value값이 총 3개 있는데, 역시 3을 출력하게 된다. 지역변수가 우선권을 가지기 때문이다.
그렇다면 맴버 변수 2을 접근할려면 어떻게 할까?
package nested.inner.ex1;
public class ShdowingMAin {
public int value = 1;
class Inner {
public int value =2;
void go() {
int value = 3;
System.out.println("value is " + value);
System.out.println("value is " + this.value);
}
}
public static void main(String[] args) {
ShdowingMAin main = new ShdowingMAin();
ShdowingMAin.Inner inner = main.new Inner();
inner.go();
}
}
this.value 로 인스턴스의 변수를 가져오면 된다.
그럼 바깥 클래스의 value는 어떻게 가져올까?
package nested.inner.ex1;
public class ShdowingMAin {
public int value = 1;
class Inner {
public int value =2;
void go() {
int value = 3;
System.out.println("value is " + value);
System.out.println("value is " + this.value);
System.out.println("ShodowMain's value is " + ShdowingMAin.this.value);
}
}
public static void main(String[] args) {
ShdowingMAin main = new ShdowingMAin();
ShdowingMAin.Inner inner = main.new Inner();
inner.go();
}
}
바깥 클래스.this.변수명 으로 하면 나온다.
이렇게 다른 변수가 가려서 안보이는 것을 새도잉이라고 한다.
다른 변수를 가리더라도 인스턴스의 참조를 사용하면 외부 변수에 접근할 수 있다.