이너 클래스는 클래스 내부에 포함되는 클래스로, 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용한다.
이너 클래스를 사용하면 접근 지정자와 관계없이 외부 클래스의 멤버들에 쉽게 접근할 수 있고, 코드의 복잡성을 줄일 수 있다.
또한 외부적으로 불필요한 데이터를 감출 수 있어 캡슐화를 달성하는데 유용하다.
이너 클래스는 인스턴스 멤버 이너 클래스, 정적 멤버 이너 클래스, 지역 이너 클래스로 나뉜다.
이너 클래스도 클래스이기 때문에 바이트 코드(.class) 파일이 생성된다.
// 외부 클래스
class Outer {
// 인스턴스 멤버 이너 클래스
class Inner {
}
// 정적 멤버 이너 클래스
static class StaticInner {
}
void run() {
// 지역 이너 클래스
class LocalInner {
}
}
}
인스턴스 멤버 이너 클래스와 정적 멤버 이너 클래스
클래스 내부에 멤버의 형태로 존재함
외부 클래스의모든 접근 지정자의 멤버에 접근 가능
바이트 코드 파일명 : '아우터클래스명$이너클래스명.class'
이너 클래스 내부에서 아우터 클래스의 멤버를 참조하려면 '아우터 클래스명.this.'를 이용하여 참조할 수 있다.
정적 멤버 이너 클래스는 인스턴스 멤버 이너 클래스에 static 키워드가 붙은 것으로 static의 특성을 따른다.(정적 멤버, 객체 생성 없이 사용 등)
인스턴스 이너 클래스 객체 생성
인스턴스 이너 클래스는 아우터 클래스 내부에 있는 멤버처럼 활용된다.
따라서 이너 클래스의 인스턴스를 생성하기 위해서는 아우터 클래스의 인스턴스를 먼저 생성한 후 참조하여야 생성할 수 있다.
// 인스턴스 이너 클래스 객체 생성
아우터 클래스 참조 변수 = new 아우터 클래스(); // 아우터 클래스 인스턴스 생성
아우터 클래스.이너 클래스 참조 변수 = 아우터 클래스 참조 변수.new 이너 클래스();
// 예시
public class Main {
public static void main(String[] args) {
A a = new A();
A.B ab = A.new B(); // A 클래스 내부에 있는 B 생성자 호출
}
}
class A {
class B {
...
}
}
public class Main {
public static void main(String[] args) {
System.out.println("외부 클래스를 사용하여 이너 클래스 기능 호출");
Outer outer = new Outer();
outer.testClass();
System.out.println();
System.out.println("이너 클래스 객체 생성하여 이너 클래스 기능 호출");
Outer.InClass inner = outer.new InClass();
inner.Test();
inner.print();
}
}
class Outer {
private int num = 1;
private static int sNum = 2;
private InClass inClass;
public Outer() {
inClass = new InClass();
}
class InClass {
int inNum = 10;
void Test() {
System.out.println("Outer num = " + num + "(외부 클래스의 인스턴스 변수)");
System.out.println("Outer sNum = " + sNum + "(외부 클래스의 정적 변수)");
}
void print() {
System.out.println("Inner inNum = " + inNum + "(이너 클래스의 인스턴스 변수)");
}
}
public void testClass() {
inClass.Test();
inClass.print();
}
}
실행 결과
정적 이너 클래스 객체 생성
인스턴스 생성 방법은 인스턴스 이너 클래스와 동일
추가적으로 static 키워드로 인해 인스턴스 생성 없이 이너 클래스의 멤버를 사용할 수 있다.
// 정적 이너 클래스 객체 생성
아우터 클래스.이너 클래스 참조 변수 = new 아우터 클래스.이너 클래스();
// 객체 생성 없이 멤버 사용
아우터 클래스.이너 클래스.멤버;
// 예시
public class Main {
public static void main(String[] args) {
// 객체 생성 후 메서드 호출
A.B ab = new A.B();
ab.print();
// 객체 생성 없이 메서드 호출
A.B.print();
}
}
class Outer { // 아우터 클래스
static class StaticInner { // 정적 이너 클래스
static void print() {
System.out.println("정적 이너 클래스");
}
}
}
지역 이너 클래스
지역 이너 클래스는 메서드 내에서 정의되는 클래스다.
지역 이너 클래스는 선언 이후 바로 인스턴스를 생성하여 사용하며, 메서드가 호출될 때만 메모리에 로딩된다. -> 정적 클래스 선언 불가
바이트 코드 파일명 : '아우터 클래스$+숫자+지역 이너클래스.class'와 같이 생성된다. 숫자가 붙는 이유 : 지역 이너 클래스는 메서드 내에서만 정의하여 메서드 호출을 통해 인스턴스 생성 및 사용하므로 여러 개의 메서드에서 동일한 클래스명을 가진 지역 이너 클래스를 생성할 수 있다. 따라서 동일한 클래스명을 가진 바이트 코드가 생성되었을 때 구분하기 위해 숫자로 마킹하는 것.
동일한 클래스명을 생성해도 예외 발생 X
지역 이너 클래스 객체 생성
아우터 클래스 멤버 + 자신이 정의된 메서드 지역 변수 사용 가능
지역 이너 클래스에서 지역 변수를 사용하기 위해서는 해당 지역 변수가 final로 선언돼 있어야 하며, final로 선언되지 않은 지역 변수는 지역 이너 클래스에서 사용할 때 컴파일러가 강제로 final로 선언한다.
-> 클래스 메모리와 스택 메모리의 차이로, 메서드의 지역 변수는 메서드가 종료되면 소멸되는 특징을 가지고 있다. 따라서 소멸된 지역 이너 클래스의 인스턴스가 소멸된 지역 변수를 참조하려는 경우가 발생할 수 있기 때문에 final로 선언하여 변수를 따로 관리하게 하는 것.
// 지역 이너 클래스의 객체 생성
지역 이너 클래스 참조 변수 = new 지역 이너 클래스();
class A {
void abc() {
class B {
}
B b = new B();
}
}
class Outer { //외부 클래스
int num = 5;
void test() {
int num2 = 6;
class LocalInClass { //지역 내부 클래스
void getPrint() {
System.out.println(num);
System.out.println(num2);
}
}
LocalInClass localInClass = new LocalInClass();
localInClass.getPrint();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}