자바 내부 클래스(inner class)란 클래스 내부에 생성된 클래스이다. 내부 클래스는 구현 위치에 따라 인스턴스 내부 클래스, 정적 내부 클래스, 지역 내부 클래스, 익명 내부 클래스로 나눌 수 있다. 하나씩 예제와 함께 살펴보자.
인스턴스 내부 클래스란 외부 클래스 멤버 변수와 동일한 위치에 구현되며 외부 클래스를 먼저 만든 후 생성되는 내부 클래스이다. 인스턴스 내부 클래스에서는 외부 클래스 변수 중 외부 인스턴스 변수와 외부 전역 변수를 사용할 수 있다.
아래는 인스턴스 내부 클래스 예제이다.
class OutClass {
private int num = 10;
private static int sNum = 120;
private InClass inClass;
public OutClass() {
inClass = new InClass();
}
// 인스턴스 내부 클래스 구현
class InClass {
int inNum = 200;
static int sInNum = 100;
void inTest() {
System.out.println(num);
System.out.println(sNum);
}
}
public void usingInTest() {
inClass.inTest();
}
}
public class innerTest {
public static void main(String[] args) {
OutClass outClass = new OutClass();
outClass.usingInTest();
}
}
예제에서처럼 인스턴스 내부 클래스는 외부 클래스의 멤버 변수와 동일한 위치에 구현되고, private으로 클래스 내부에서만 접근할 수 있도록 제한하여 사용하는 경우가 많다. 또한 인스턴스 내부 클래스는 객체가 생성되어야만 사용할 수 있으므로 static 변수를 사용할 수 없다.
정적 내부 클래스란 외부 클래스 멤버 변수와 동일한 위치에 구현되며 외부 클래스 생성과 무관하게 생성되는 내부 클래스이다. 정적 내부 클래스에서는 외부 클래스 변수 중 외부 전역 변수만을 사용할 수 있다.
아래는 정적 내부 클래스 예제이다.
class OutClass {
private int num = 10;
private static int sNum = 120;
// 정적 내부 클래스 구현
static class InStaticClass {
int iNum = 100;
static int sInNum = 200;
void inTest() {
// num += 10; //외부 클래스의 인스턴스 변수는 쓰지 못함
sNum += 10; // 외부 클래스의 static 변수는 쓸 수 있음
System.out.println(sNum);
System.out.println(iNum);
System.out.println(sInNum);
}
static void sTest() {
System.out.println(sNum);
System.out.println(sInNum);
}
}
}
public class innerTest {
public static void main(String[] args) {
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
sInClass.inTest(); // 인스턴스 메서드
OutClass.InStaticClass.sTest(); // 정적 메서드
}
}
예제에서처럼 정적 내부 클래스는 외부 클래스의 멤버 변수와 동일한 위치에 구현되고, 메서드에서 외부 클래스의 인스턴스 변수를 업데이트할 수 없다.
지역 내부 클래스란 외부 클래스의 메서드 내부에 구현되며 메서드를 호출할 때 생성되는 내부 클래스이다. 지역 내부 클래스에서는 외부 클래스 변수 중 외부 인스턴스 변수와 외부 전역 변수를 사용할 수 있다.
아래는 지역 내부 클래스 예제이다.
class Outer {
int outNum = 100;
static int sNum = 200;
public Runnable getRunnable(int i) {
int localNum = 150;
// 지역 내부 클래스 구현
class MyRunnable implements Runnable {
@Override
public void run() {
localNum += 100; // error. 외부 클래스 메소드의 지역 변수 수정 불가
i += 100; // error. 외부 클래스 메소드의 매개변수 수정 불가
System.out.println(outNum);
System.out.println(sNum);
System.out.println(localNum);
System.out.println(i);
}
}
return new MyRunnable();
}
}
public class LocalInnerTest {
public static void main(String[] args) {
Outer outer = new Outer();
outer.getRunnable(2).run();
}
}
예제에서처럼 지역 내부 클래스는 외부 클래스의 메서드에 구현되고, 외부 클래스의 인스턴스 변수와 전역 변수를 사용할 수 있다. 하지만 메서드 내부에서만 사용되는 지역 변수와 매개 변수는 지역 내부 클래스에서 변경할 수 없다. 원래 매개 변수와 지역 변수는 메소드 호출 시 스택 메모리 영역에 생성되었다가 메소드 사용이 종료되면 스택 메모리 영역에서 사라진다. 외부 클래스의 메소드 호출 + 종료 후에 내부 클래스의 메소드가 호출되기 때문에 만약 외부 클래스 메소드의 지역 변수와 매개 변수가 스택 메모리에 들어간다면, 내부 클래스에서 이것을 사용할 수 없다. 그러므로 java8 이후로는 외부 클래스의 메소드에 내부 클래스가 구현된 상황이라면 해당 메소드의 매개 변수와 지역 변수를 내부적으로 final로 선언하여 클래스(정적) 메모리 영역에 저장하여 내부 클래스에서도 이것들에 접근할 수 있도록 하였다. (하지만 final로 선언된 변수들이기 때문에 수정은 할 수 없다.)
익명 내부 클래스란 메서드 내부에 구현되거나 변수에 대입하여 직접 구현되며 이름이 없는 내부 클래스이다. 익명 내부 클래스는 메서드를 호출할 때 생성되거나, 인터페이스 타입 변수에 대입할 때 new 예약어를 사용하여 생성할 수 있다. 또한, 외부 인스턴스 변수와 외부 전역 변수를 사용할 수 있다.
아래는 익명 내부 클래스 예제이다.
class Outer {
int outNum = 100;
static int sNum = 200;
// 익명 내부 클래스, 변수에 대입하여 구현
// Runnable: 자바 내장 interface
Runnable runnable = new Runnable() {
public void run() {
System.out.println("runnable");
};
};
public Runnable getRunnable(int i) {
int localNum = 150;
// 익명 내부 클래스, 메서드 내에 구현
return new Runnable() {
public void run() {
outNum += 10;
System.out.println(outNum);
System.out.println(sNum);
System.out.println(localNum);
System.out.println(i);
};
};
}
}
public class LocalInnerTest {
public static void main(String[] args) {
Outer outer = new Outer();
outer.getRunnable(2).run();
outer.runnable.run();
}
}
위 예제처럼 익명 내부 클래스는 변수에 대입하거나 외부 클래스의 메소드 내에 구현된다. 또한, 이름에서 유추할 수 있듯이 이름 없이 객체를 생성하는 클래스이다.