
'김영한의 실전 자바 - 중급 1편' 강의를 들으면서 복습할만한 내용을 정리하였다.

중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류할 수 있다.
변수의 선언 위치
중첩 클래스의 선언 위치
정적 중첩 클래스 -> 정적 변수와 같은 위치
내부 클래스 -> 인스턴스 변수와 같은 위치
지역 클래스 -> 지역 변수와 같은 위치
class Outer {
...
//정적 중첩 클래스
static class StaticNested {
...
}
//내부 클래스
class Inner {
...
}
}
정적 중첩 클래스는 정적 변수와 같이 앞에 static 이 붙어있다.
내부 클래스는 인스턴스 변수와 같이 앞에 static 이 붙어있지 않다.
class Outer {
public void process() {
//지역 변수
int lcoalVar = 0;
//지역 클래스
class Local {...}
Local local = new Local();
}
}
중첩 vs 내부
중첩(Nested) : 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계
내부(Inner) : 나의 내부에 있는 나를 구성하는 요소
public class NestedOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
static class Nested {
private int nestedInstanceValue = 1;
public void print() {
// 자신의 멤버에 접근
System.out.println(nestedInstanceValue);
// 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
//System.out.println(outInstanceValue);
// 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
System.out.println(outClassValue);
}
}
}
정적 중첩 클래스 앞에는 static이 붙는다.
바깥 클래스의 인스턴스에 소속되지 않는다.
바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
바깥 클래스의 클래스 멤버에는 접근할 수 있다.
(다른 외부 클래스에서도 바깥 클래스의 클래스 멤버에 접근할 수 있지만, 정적 중첩 클래스는 바깥 클래스의 클래스 멤버의 접근 제어자가 private 인 경우에도 접근할 수 있다는 뜻)
정적 중첩 클래스 예시
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
System.out.println("nested.getClass() = " + nested.getClass());
}
}
new 바깥클래스.중첩클래스() 로 생성할 수 있다.NestedOuter.Nested 와 같이 바깥클래스.중첩클래스 로 접근할 수 있다.new NestedOuter() 로 만든 바깥 클래스의 인스턴스와 new NestedOuter.Nested() 로 만든 정적 중첩 클래스의 인스턴스는 서로 아무 관계가 없는 인스턴스이다. 단지 클래스 구조상 중첩해 두었을 뿐이다.인스턴스가 생성된 상태

바깥 클래스의 멤버에 접근

Nested.print() 를 살펴보자
정적 중첩 클래스는 바깥 클래스의 정적 필드에는 접근할 수 있다. 하지만 바깥 클래스가 만든 인스턴스 필드에는 바로 접근할 수 없다. 바깥 인스턴스의 참조가 없기 때문이다.
정리
정적 중첩 클래스는 사실 다른 클래스를 그냥 중첩해 둔 것일 뿐이다!! 쉽게 이야기해서 둘은 아무런 관계가 없다. 그냥 클래스 2개를 따로 만든 것에서 private 접근 제어자에 접근할 수 있다는 정도가 차이점이다.
정적 중첩 클래스의 활용
중첩 클래스(내부 클래스 포함)는 그 용도가 자신이 소속된 바깥 클래스 안에서 사용되는 것이다.
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(outClassValue);
}
}
}
내부 클래스 앞에는 static이 붙지 않는다.
바깥 클래스의 인스턴스에 소속된다.
내부 클래스 예시
public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter outer = new InnerOuter();
InnerOuter.Inner inner = outer.new Inner();
inner.print();
System.out.println("inner.getClass() = " + inner.getClass());
}
}
내부 클래스는 바깥 클래스의 인스턴스에 소속된다. 따라서 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.
내부 클래스는 new 바깥클래스의 인스턴스 참조.내부클래스() 로 생성할 수 있다.
내부 클래스는 바깥 클래스의 인스턴스에 소속되어야 한다. 따라서 내부 클래스를 생성할 때, 바깥 클래스의 인스턴스 참조가 필요하다.
outer.new Inner() 에서 outer 는 바깥 클래스의 인스턴스 참조를 가진다.
outer.new Inner() 로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성된다.
따라서 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.
개념 - 내부 클래스의 생성

개념상 바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.
따라서 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스의 멤버에 접근할 수 있다.
실제 - 내부 클래스의 생성

실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아니다. 하지만 개념상 인스턴스 안에 생성된다고 이해하면 충분하다.
실제 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있다.
논리적 그룹화 : 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화 된다. 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
캡슐화 : 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다. 이는 곧 캡슐화를 높인다고 할 수 있다.
같은 이름의 바깥 변수 접근
변수의 이름이 같기 때문에 어떤 변수를 먼저 사용할지 우선 순위가 필요하다.
프로그래밍에서 우선 순위는 대부분 더 가깝거나, 더 구체적인 것이 우선권을 가진다. 쉽게 이야기해서 사람이 직관적으로 이해하기 쉬운 방향으로 우선 순위를 설계한다.프로그래밍에서 가장 중요한 것은 명확성이다. 이름이 같은 경우 처음부터 이름을 서로 다르게 지어서 명확하게 구분하는 것이 더 나은 방법이다.