중첩 클래스
사용하는 이유
- 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화 됨
- 캡슐화: 중첩 클래스는 바깥 클래스의
private 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거
당연히 일반 클래스 처럼 인터페이스를 구현하거나 부모 클래스를 상속 할 수 있음
정적 중첩 클래스
static이 붙음- 바깥 클래스의 인스턴스에 소속되지 않음
바깥 클래스의 인스턴스 접근
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| 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(NestedOuter.outClassValue);
}
}
}
|
사용 방법
1
2
3
4
5
6
7
| public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
}
}
|
내부 클래스
static이 붙지 않음- 바깥 클래스의 인스턴스에 소속
바깥 클래스의 인스턴스 접근
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| 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);
}
}
}
|
사용 방법
1
2
3
4
5
6
7
| public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter outer = new InnerOuter();
InnerOuter.Inner inner = outer.new Inner();
}
}
|
같은 이름의 바깥 변수 접근
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public class ShdowingMain {
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("ShdowingMain.value = " + ShdowingMain.this.value);
}
}
}
|
지역 클래스
- 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언
- 지역 클래스는 지역 변수에 접근할 수 있음
- 접근 제한자를 사용할 수 없음
사용 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public class LocalOuterV1 {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
class LocalPrinter {
int value = 0;
public void printData() {
System.out.println("value=" + value);
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
}
}
|
의문: 메서드 실행이 종료 된 후 지역 클래스로 생성한 객체에서 지역 변수에 접근한다면?
변수의 생명 주기
- 클래스 변수: 프로그램 종료 까지. (메서드 영역)
- 인스턴스 변수: 인스턴스의 생존 기간(힙 영역)
- 본인이 소속된 인스턴스가 GC 되기 전까지 존재
- 지역 변수: 메서드 호출이 끝나면 사라짐(스택 영역)
지역 변수는 스택 영역에 있기 때문에 메서드 실행이 종료되면 사라지기 때문에 접근이 불가능 해야 합니다.
1
2
3
| public interface Printer {
void print();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| public class LocalOuterV3 {
private int outInstanceVar = 3;
public Printer process(int paramVar) {
int localVar = 1; //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
class LocalPrinter implements Printer {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
//인스턴스는 지역 변수보다 더 오래 살아남는다.
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
//printer.print();를 여기서 실행하지 않고 Printer 인스턴스만 반환한다.
return printer;
}
public static void main(String[] args) {
LocalOuterV3 localOuter = new LocalOuterV3();
Printer printer = localOuter.process(2);
//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
printer.print();
}
}
|
1
2
3
4
| value=0
localVar=1
paramVar=2
outInstanceVar=3
|
실행 결과 여전히 지역변수 값을 참조할 수 있습니다. 이는 지역 클래스가 객체를 생성할 때, 지역 변수를 캡처하기 때문에 일어나는 현상입니다.
지역 변수 캡처
지역 클래스의 인스턴스 생성 시
- 복사한 지역 변수를 인스턴스에 포함
- 복사한 지역 변수를 포함해서 인스턴스 생성 완료
이렇게 생성된 인스턴스에 있는 캡처한 변수에 접근하기 때문에 지역 변수의 생명 주기와 무관하게 캡처 변수에 접근 할 수 있습니다.
지역 변수 값 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| public class LocalOuterV4 {
private int outInstanceVar = 3;
public Printer process(int paramVar) {
int localVar = 1; //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
class LocalPrinter implements Printer {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
//인스턴스는 지역 변수보다 더 오래 살아남는다.
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
// localVar = 10; 컴파일 오류
// paramVar = 20; 컴파일 오류
return printer;
}
}
|
캡처 변수의 값을 변경하지 못하는 이유
- 지역 변수의 값과 인스턴스에 있는 캡처 변수의 값을 서로 동기화 해야 하는데, 멀티쓰레드 상황에서 이런 동기화는 매우 어렵고, 성능에 나쁜 영향을 미칠 수 있음
- 예상하지 못한 곳에서 값이 변경 될수 있고, 이는 디버깅을 어렵게 함
익명 클래스
사용 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| public class Ex1RefMainV3 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
Process dice = new Process() {
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
}
};
Process sum = new Process() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
};
System.out.println("Hello 실행");
hello(dice);
hello(sum);
}
}
|
람다를 사용한 더 간결한 문법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| public class Ex1RefMainV5 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
System.out.println("Hello 실행");
hello(() -> {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
});
hello(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
});
}
}
|
참고