class A {
class B {...} // 또 다른 클래스
}
클래스 안에 정의된 클래스를 가리켜 '네스티드 클래스(Nested Class)'라고 한다. 그리고 감싸는 클래스를 가리켜 '외부 클래스(Other Class)'라고 한다.
클래스 내에 정의되어 있는 모든 클래스를 카리켜 '네스티드 클래스'라고 한다. 하지만 static 선언 여부를 기준으로 다음과 같이 나뉜다.
class A {
static class StaticClass {...}
}
class B {
class InnerClass {...}
}
이중에서 Non-static 네스티드 클래스를 가리켜 '이너(Inner) 클래스'라 한다.
그리고 이너 클래스는 정의되는 곳에 따라 세 종류로 나뉜다.
static 네스티드 클래스는 자신을 감싸는 외부 클래스의 인스턴스와 상관없이 생성이 가능하다.
class A {
private static int num = 0;
static class Nested1 {
void add(int n) { num += n; }
}
static class Nested2 {
void get() { return num; }
}
}
class Main {
public static void main(String[] args) {
A.Nested1 nst1 = new A.Nested1();
nst1.add(5);
A.Nested2 nst2 = new A.Nested2();
System.out.println(nst2.get());
}
}
결과: 5
위의 예제의 A 클래스 내에 두 개의 static가 정의되어 있다. Nested1, Nested2 클래스 내에서는 Outher의 static 멤버 num에 접근하고 있다. private로 선언되어 있어도 접근이 가능하다. 따라서 A의 static 멤버 num Nested1, Nested2의 모든 인스턴스가 공유하게 된다. 이것이 static 네스티드 클래스가 갖는 주요 특징이다.
그리고 또 다른 특징은 Static 네스티드 클래스 내에서 외부 클래스의 인스턴스 변수와 메소드에 접근이 불가능하다.
즉 Static 네스티드 클래스 내에서는 외부 클래스의 static으로 선언되 변수와 메소드에만 접근이 가능하다.
static가 붙지 않은 네스티드 클래스가 '이너 클래스'이다. 그리고 다음과 같이 3가지 있다.
이 중에서 다음 둘은 정의된 위치에 따라서 구분이 된다.
class A {
class MemberInner {...} // 멤버 클래스
void method() {
class LocalInner {...} // 로컬 클래스
}
}
class Outer {
private int num = 0;
class Member {
void add(int n) { num += n; }
int get() { return num; }
}
}
class Main {
public static void main(String[] args) {
Outer o1 = new Outer();
Outer o2 = new Outer();
// o1 기반으로 두 인스턴스 생성
Outer.Member o1m1 = o1.new Member();
Outer.Member o1m2 = o1.new Member();
// o2 기반으로 두 인스턴스 생성
Outer.Member o2m1 = o2.new Member();
Outer.Member o2m2 = o2.new Member();
// o1 기반으로 생성된 두 인스턴스의 메소드 호출
o1m1.add(5);
System.out.println(o1m2.get());
// o2 기반으로 생성된 두 인스턴스의 메소드 호출
o2m1.add(7);
System.out.println(o2m2.get());
}
}
5
7
위에서 멤버 클래스는 다음과 같다.
class Outer {
private int num = 0;
class Member { // 멤버 클래스의 정의
void add(int n) { num += n }
int get() { return num; }
}
}
Member 클래스 내에서는 Outer 클래스의 변수에 접근이 가능하다. 인스턴스 변수 private으로 선언이 되어 있어도 가능하다. 멤버 클래스의 특징은 다음과 같다.
"멤버 클래스의 인스턴스는 외부 클래스의 인스턴스에 종속적이다."
"클래스의 정의를 감추어야 할 때"
interface Printable {
void print();
}
class Papers {
private String con;
public Papers(String s) {
con = s;
}
public Printable getPrinter() {
return new Printer();
}
private class Printer implements Printable {
public void print() {
System.out.println(con);
}
}
}
class UseMemberInner {
public static void main(String[] args) {
Papers p = new Papers("서류 내용: 행복합니다.");
Printable prn = p.getPrinter();
prn.print();
}
}
다음 멤버 클래스를 보자.
private class Printer implements Printable {
public void print() {
System.out.println(con);
}
}
private로 선언이 되면, 이 클래스 정의를 감싸는 클래스 내에서만 인스턴스 생성이 가능하다. 때문에 이 Printer 인스턴스는 다음과 같은 방법으로만 참조가 가능하다.
public static void main(String[] args) {
Papers p = new Papers("서류 내용: 행복합니다.");
Printalbe prn = p.getPrinter();
....
}
이제 Papers 클래스의 외부에서는 getPrinter 메소드가 어떠한 인스턴의 참조 값을 반환하는지 알지 못한다. 다만 반환되는 참조 값의 인스턴스가 Printable을 구현하고 있어서 Printable의 참조변수로 참조할 수 있다는 사실만 알뿐이다. 그리고 이러한 상황을 “클래스의 정의가 감추어진 상황”이라고 한다.
이렇게 클래스의 정의를 감추면, getPrinter 메소드가 반환하는 인스턴스가 다른 클래스의 인스턴스로 변경되어도 Papers 클래스 외부의 코드는 조금도 수정할 필요가 없게 된다.
interface Printable {
void print();
}
class Papers {
private String con;
public Papers(String s) { con = s; }
public Printable getPrinter() {
class Printer implements Printable {
public void print() { System.out.println(con); }
}
return new Printer();
}
}
class UseLocalInner {
public static void main(String[] args) {
Papers p = new Papers("서류 내용: 행복합니다.");
Printable prn = p.getPrinter();
prn.print();
}
}
이전 클래스와의 차이점은 메소드 안에 Printer의 정의가 위치한다는 것이다.
public Printable getPrinter() {
class Printer implements Printable [
public void print() {
System.out.println(con);
}
}
return new Printer();
}
이렇게 메소드 내에 클래스를 정의하게 되면 해당 메소드 내에서만 인스턴스 생성이 가능하다. (따라서 클래스에 대한 private 선언은 의미가 없다. 어차피 메소드 내에서만 인스턴스 생성이 가능해서) 즉 멤버 클래스보다는 더 깊이, 특정 블록에 감추는 효과가 있다.
익명 클래스는 람다랑도 관계가 있다.
public Printable getPrinter() {
class Printer implements Printable { // Printer의 정의
public void print() {
System.out.println(con);
}
}
return new Printer(); // Printer 인스턴스의 생성
}
위의 코드를 익명 클래스로 나타내면
public Printable getPrinter() {
return new Printable() {
public void print() {
System.out.println(con);
}
};
}
따라서 익명 클래스는 new Printable()에 이어 등장하는 이름 없는 클래스의 정의를 가르켜 '익명 클래스'라고 한다.
interface Printable {
void print();
}
class Papers {
private String con;
public Papers(String s) { con = s; }
public Printable getPrinter() {
return new Printable() {
public void print() { System.out.println(con); }
};
}
}
class UseAnonymousInner {
public static void main(String[] args) {
Papers p = new Papers("서류 내용: 행복합니다.");
Printable prn = p.getPrinter();
prn.print();
}
}
참고: 윤성우의 열혈 Java 프로그래밍