JAVA - 내부클래스(innerClass또는 nestedClass)

클래스 안에 클래스, 즉 내부 클래스를 생성할 수 있다. 중첩되는 객체이므로 nested class라고도 하고, 그냥 inner class라고도 하는 것 같다. 내부 클래스는 크게 4가지로 분류된다. 일반 내부 클래스, 내부 static 클래스, 내부 지역 중첩 클래스, 익명 중첩 클래스가 있다.


일반 내부 클래스

클래스 안에 생성되는 또 다른 클래스를 의미한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package innerClass;

public class InnerExam1 {

class Cal {
int value = 0;
public void plus() {
value++;
}
}

public static void main(String[] args) {
InnerExam1 ex = new InnerExam1();
InnerExam1.Cal cal = ex.new Cal();

cal.plus();
System.out.println(cal.value);
}

}

main메서드에서 사용하려면 우선 외부 클래스에 대한 인스턴스를 생성하고, 내부 클래스에 대한 인스턴스를 생성해서 사용해야 한다.

1
2
InnerExam1 ex = new InnerExam1();
InnerExam1.Cal cal = ex.new Cal();

내부 static 클래스

클래스 안에서 static 키워드로 생성되는 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package innerClass;

public class InnerExam2 {

static class Cal {
int value = 0;
public void plus() {
value=value+2;
}
}

public static void main(String[] args) {
Cal cal = new Cal();

cal.plus();
System.out.println(cal.value);
}

}

static 키워드로 내부 클래스를 생성했으므로 외부 클래스에 대한 인스턴스를 생성할 필요없이 바로 내부 클래스에 해당하는 인스턴스를 생성해서 사용할 수 있다.

1
InnerExam2.Cal cal = new InnerExam2.Cal();

내부 지역 중첩 클래스

메서드 안에 생성되는 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package innerClass;

public class InnerExam3 {

public void exec() {
class Cal {
int value = 0;
public void plus() {
value++;
}
}

Cal cal = new Cal();
cal.plus();
System.out.println(cal.value);
}

public static void main(String[] args) {
InnerExam3 ex = new InnerExam3();
ex.exec();
}

}
1
2
// 실행결과
3

처음에 좀 이해하기 어렵고 난해하다고 생각했다. 왜 난해하다고 생각했냐면, main 메서드에서 인스턴스를 생성할 때 일반적인 내부 클래스(nested class)를 생성하듯이 InnerExam3.Cal cal = ex.new Cal(); 로 생성이 가능하다고 생각했기 때문이다.

1
2
InnerExam3.Cal cannot be resolved to a type
Cannot allocate the member type InnerExam3.Cal using its compound name when qualified by an enclosing instance. The member type name is resolved relatively to the qualifying instance type

에러 메세지를 해석해보면, 인스턴스를 InnerExam3.Cal 객체 타입을 사용할 수 없다고 하는 내용이다. 다시 보니 내부 지역 중첩 클래스는 클래스 안에 클래스인건 맞으나, 메서드 안에서 선언된 내장 객체이므로 main 메서드와 동일한 클래스에 있었기 때문에 두번 인스턴스를 생성할 필요가 없던 것이다.
(쓰면서도 말이 이해하기 좀 난해한것 같지만…)

따라서 그냥 외부 클래스에 대한 인스턴스 하나만 생성해서 내부 메서드를 실행함으로써 중첩 클래스를 사용할 수 있었다.


익명클래스

말그대로 이름이 없는 클래스이다. 상속받는 클래스를 굳이 만들 필요가 없을 때 사용한다고 한다. 예제 코드를 통해 알아보자.

Action이라는 이름의 추상클래스와 이 Action을 상속받는 MyAction 클래스가 있다고 가정하자.

Action.class

1
2
3
4
5
package nestedClass;

public abstract class Action {
public abstract void exec();
}

MyAction.class

1
2
3
4
5
6
7
8
9
package nestedClass;

public class MyAction extends Action {

@Override
public void exec() {
System.out.println("exec");
}
}

MyAction 클래스로부터 인스턴스를 생성해서 메소드를 호출하는 클래스 ActionExam이다.

ActionExam.class

1
2
3
4
5
6
7
8
9
package nestedClass;

public class ActionExam {
public static void main(String[] args) {

Action action = new MyAction();
action.exec();
}
}
1
2
// 실행결과
exec

여기서 MyAction 클래스는 한 번만 쓰이고 다른 어디에서도 사용되지 않는 클래스라고 가정해보자. 이 경우라면, 굳이 MyAction을 생성하기보다는 그냥 ActionExam에서 익명클래스를 선언해서 사용할 수 있다.

ActionExam.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package innerClass;

public class ActionExam {
public static void main(String[] args) {
// Action action = new MyAction();
// action.exec();

Action action = new Action() {

@Override
public void exec() {
System.out.println("exec");
}
};
action.exec();
}
}

이렇게 하면, Action클래스를 상속받는 구현체를 생성하지 않아도 main 함수 내에서 익명클래스를 선언해서 사용할 수 있다.