[JAVA] 중첩 클래스와 내부 클래스
[JAVA] 중첩 클래스와 내부 클래스
중첩 클래스
중첩 클래스는 바깥 클래스의 안에 있지만, 바깥 클래스와 상관 없는 전혀 다른 클래스
1
2
3
4
5
6
7
class Outer {
...
//중첩 클래스
class Nested {
...
}
}
중첩 클래스의 종류
- 정적 중첩 클래스 -
static이 붙고, 바깥 클래스의 인스턴스에 소속되지 않음 - 내부 클래스 -
static이 붙지 않고, 바깥 클래스의 인스턴스에 소속- 내부 클래스 - 바깥 클래스의 인스턴스의 멤버에 접근
- 지역 클래스 - 내부 클래스의 특징 + 지역 변수에 접근
- 익명 클래스 - 지역 클래스의 특징 + 클래스의 이름이 없는 특별 클래스
중첩(Nested): 내 안에 있지만 내것이 아닌 것
내부(Inner): 나의 내부에 있는 나를 구성하는 요소
중첩 클래스의 선언 위치
- 정적 중첩 클래스(static) → 정적 변수와 같은 위치
- 내부 클래스 → 인스턴스 변수와 같은 위치
- 지역 클래스 → 지역 변수와 같은 위치
정적 중첩 클래스는 바깥 클래스 안에 있지만 바깥 클래스와 관계없는 다른 클래스 내부 클래스는 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소
중첩 클래스를 왜 사용하는지?
- 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 긴밀하게 연결되어 있는 경우에 사용. 외부의 여러 클래스가 특정 중첩 클래스를 사용한다면 중첩 클래스로 만들면 안된다.
- 논리적으로 그룹화가 가능하다. 다른 곳에 필요없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
- 중첩 클래스는 바깥 클래스의
private멤버에 접근할 수 있다. 이로 인해 불필요한public메서드를 제거 할 수 있다. 즉 캡슐화가 가능하다.
중첩 클래스 예제
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 outInstancValue = 2;
//정적 중첩 클래스는 앞에 static <- 붙음.
static class Nested {
private int nestedInstanceValue = 1;
public void print() {
//ㅈㅏ신의 멤버에 접근
System.out.println(nestedInstanceValue);
// 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
// System.out.println(outInstancValue);
//바깥 클래스의 클래스 멤버에는 접근할 수 있다 private도 접근 가능
System.out.println(NestedOuter.outClassValue);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter.Nested nested = new NestedOuter.Nested(); //정적 중첩 클래스 생성
nested.print();
System.out.println("nestedClass = " + nested.getClass());
}
}
//실행결과
1
3
nestedClass = class nasted.nasted.NestedOuter$Nested
- 정적 중첩 클래스는
new 바깥클래스.중첩클래스()로 생성가능 NestedOuter.Nested와 같이바깥 클래스.중첩클래스로 접근 가능
private 접근 제어자
- 같은 클래스 안에 있을 때만
private에 접근할 수 있다. - 중첩 클래스도 바깥 클래스와 같은 클래스 안에 있으므로, 바깥
private접근제어자에 접근할 수 있다.
정적 중첩 클래스는 다른 클래스를 중첩해 둔 것일 뿐. 둘은 아무런 관계가 없다. 그냥 클래스 두개를 만든 것과 같다.
내부 클래스
내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소로 바깥 클래스의 인스턴스에 소속된다.
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;
//static이 붙지 않음 즉 인스턴스 멤버가 된다.
class Inner {
private int innerInstanceValue = 1;
public void print() {
// 자기 자신에 접근
System.out.println(innerInstanceValue);
//외부 클래스의 인스턴스 멤버에 접근 가능 private도 접근 가능
System.out.println(outInstanceValue);
// 외부 클래스의 클래스 멤버에는 접근 가능. private 접근 가능
System.out.println(InnerOuter.outClassValue);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
InnerOuter outer = new InnerOuter(); //x001
InnerOuter.Inner inner = outer.new Inner(); //x001 의 내부에다 inner를 만든다
inner.print(); //그래서 바깥 인스턴스 접근 가능
System.out.println(inner.getClass());
//실행결과
1
2
3
class nasted.inner.InnerOuter$Inner
- 내부 클래스는 바깥 클래스의 인스턴스에 소속되어 바깥 클래스의 인스턴스 정보를 알아야 생성가능
- 내부 클래스는
바깥클래스의 인스턴스 참조.new 내부클래스()로 생성 가능- 바깥 클래스의 인스턴스 참조가 필요
outer.new Inner();로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성- 따라서 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스 생성 가능
private 접근 제어자
- 같은 클래스 안에 있을 때만
private에 접근할 수 있다. - 내부/바깥 = 같은 클래스 따라서 내부 클래스는 바깥 클래스의
private접근 제어자에 접근 가능
정적 중첩 클래스는 다른 클래스를 그냥 중첩해 둔 것일 뿐. 아무런 관계가 없다 반면 내부 클래스는 바깥 클래스의 인스턴스 내부에서 구성요소로 사용된다.
지역 클래스
지역 클래스는 내부 클래스의 종류 중 하나이므로 내부 클래스의 특징을 그대로 따른다.
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
package nasted.local;
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); // 바깥 클래스의 인스턴스 멤버
}
}
LocalPrinter printer = new LocalPrinter();
printer.printData();
}
public static void main(String[] args) {
LocalOuterV1 localOuter = new LocalOuterV1();
localOuter.process(2);
}
}
- 지역 클래스는 지역변수와 같이 코드블럭 안에 클래스를 선언한다.
- 지역 클래스는 지역변수에 접근가능
- 지역 변수와 같이 접근 제어자 사용 불가
지역 변수 캡쳐
자바는 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해 생성된 인스턴스에 함께 넣어둔다. 이 과정을 변수 캡쳐라 한다.
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class LocalOuterV3 {
private int outInstanceVar = 3;
public Printer process(int paramVar) {
int localVar = 1; //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
//localVar = 2; // <- 이렇게 바꾸면 컴파일 에러!
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();
//추가
System.out.println("ㅡㅡㅡㅡ필드확인 ㅡㅡㅡㅡㅡ");
Field[] fields = printer.getClass().getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
}
}
//실행결과
value = 0
localVar = 1
paramVar = 2
outInstanceVar = 3
ㅡㅡㅡㅡ필드확인 ㅡㅡㅡㅡㅡ
//인스턴스 변수
field = int nasted.local.LocalOuterV3$1LocalPrinter.value
//캡처 변수
field = final int nasted.local.LocalOuterV3$1LocalPrinter.val$localVar
field = final int nasted.local.LocalOuterV3$1LocalPrinter.val$paramVar
//바깥 클래스 참조
field = final nasted.local.LocalOuterV3 nasted.local.LocalOuterV3$1LocalPrinter.this$0
process()메서드가 종료된 이후 메인에서LocalPrinter.print()를 호출한다 하지만print()메서드는 지역 변수는paramVarlocalVar에 접근해야 한다.- 하지만
process()메서드가 종료됨으로 지역변수는 제거됐다. 그러나 값들은 정상출력 된다. - 지역 클래스가 접근하는 지역 변수는 절대 값이 변해선 안된다.
- 스택 영역에 존재하는 지역 변수의 값과 인스턴스에 캡처한 캡처 변수의 값이 서로 달라지는 동기화 문제가 발생할 수 있기 때문이다. 이로 인해 많은 문제가 발생할 수 있다
final키워드를 넣지 않았을 뿐 사실상final키워드를 넣은 값을 변경하지 않는 지역변수
익명 클래스
익명 클래스는 지역 클래스 종류 중 하나. 익명 클래스는 지역 클래스인데, 클래스의 이름이 없는 특징이 있다.
지역 클래스의 선언과 생성을 한번에 가능
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
public class AnonymusOuter {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1; //지역 변수
//익명 클래스는 클래스의 본문의 정의하며 생성
//printer를 구현 하며 바로 생성
Printer printer = new 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); // 바깥 클래스의 인스턴스 멤버
}
};
printer.print();
System.out.println("printer.class" + printer.getClass());
}
public static void main(String[] args) {
AnonymusOuter main = new AnonymusOuter();
main.process(2);
}
}
- 익명 클래스는 클래스의 바디를 정의하면서 동시에 생성
new다음에 구현하거나 상속할 인터페이스나 추상 클래스를 바로 작성Printer를 생성하는 것 같지만Printer라는 이름의 인터페이스를 구현한 익명 클래스를 생성- 즉,
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
public class Ex1RefMain5 {
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 = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
});
}
}
- 메서드(함수)를 인수로 전달할 수 있게 해주는 람다 사용
- 람다식은 익명 클래스를 더 간결하게 표현한 문법
- 내부적으로는 익명 클래스처럼 동작함
This post is licensed under
CC BY 4.0
by the author.