내부 클래스
- 내부 클래스는 말 그대로 클래스 내부에 선언한 클래스다.
- 내부 클래스를 선언하는 이유는 외부 클래스와 밀접한 관련이 있기 때문이다.
- 다른 클래스와 협력할 일이 없을때도 내부 클래스로 선언해서 사용한다.
- 인스턴스 내부 클래스, 정적 내부 클래스, 지역 내부 클래스가 있다.
- 클래스 이름 없이 선언하고 바로 쓸수 있는 익명 클래스도 있다.
class A{ // 외부 클래스
class B{ // 인스턴스 내부 클래스
static class C{ // 정적 내부 클래스
}
}
public void a(){
class D{ // 지역 내부 클래스
}
}
}
인스턴스 내부 클래스
- 인스턴스 변수를 선언할때와 같은 위치에 선언하며, 외부 클래스 내부에서만 생성하여 사용하는 객체를 선언할때 사용한다.
- 인스턴스 내부 클래스는 외부 클래스 생성 후 생성된다. 외부 클래스를 생성하지 않으면 내부 클래스를 사용할 수 없다.
public class OutClass {
private int num = 10;
private static int sNum = 20;
private InClass inClass;
public OutClass() {
inClass = new InClass();
}
class InClass {
int inNum = 100;
// static int sInNum = 200; 내부 클래스에서 정적변수 선언 불가능
void inTest() {
System.out.println("OutClass num: " + num);
System.out.println("OutClass sNum: " + sNum);
}
// static void sTest(){} 마찬가지로 내부클래스에서 정적메소드 선언 불가능
}
public void usingClass() {
inClass.inTest();
}
}
public class InnerTest {
public static void main(String[] args) {
OutClass outClass = new OutClass();
outClass.usingClass();
}
}
인스턴스 내부 클래스에서 사용하는 변수와 메소드
- 외부 클래스에서 private로 선언했더라도 내부 클래스에서 사용할 수 있다.
- 내부 클래스에서는 정적선언을 할 수 없다.
- 외부 클래스의 인스턴스를 만들어 내부클래스의 메소드를 호출할 수 있다.
다른 클래스에서 인스턴스 내부 클래스 생성하기
- 내부클래스를 생성하는 이유는 그 클래스를 감싸고 있는 외부 클래스에서만 사용하기 위해서다.
- 내부클래스를 밖의 다른 클래스를 생성하서 사용하는것은 용도에 맞지 않다.
정적 내부 클래스
- 외부 클래스가 먼저 생성되고 나서 내부 클래스가 생성되는 환경에서는 정적선언을 할 수 없다고 했다.
- 하지만 내부 클래스가 외부 클래스와 무관하게 사용가능해야 한다면
static
예약어를 사용하면 된다. - 그러나 정적내부 클래스를 생성할경우 정적으로 선언된 변수만 참조할 수 있다.
지역 내부 클래스
- 메소드 내부에 클래스를 정의해 사용하는것을 말한다.
Runnable
인커페이스를 구현하는 클래스를 지역 내부 클래스로 만든 예제.
class Outer {
int outNum = 100;
static int sNum = 200;
Runnable getRunnable(int i) {
int num = 100;
class MyRunnable implements Runnable {
int localNum = 10;
@Override
public void run() {
// num = 200; 지역변수는 상수로 바껴 불가능
// i = 100; 매개변수 역시 상수화
System.out.println("i: " + i);
System.out.println("num: " + num);
System.out.println("localNum: " + localNum);
System.out.println("outNum: " + outNum);
System.out.println("Outer.sNum: " + Outer.sNum);
}
}
return new MyRunnable();
}
}
public class LocalInnerTest {
public static void main(String[] args) {
Outer out = new Outer();
Runnable runner = out.getRunnable(10);
runner.run();
}
}
- 아래는 실행결과
i: 10
num: 100
localNum: 10
outNum: 100
Outer.sNum: 200
Outer
클래스를 생성한 후Runnable
형 객체로getRunnable()
을 호출하는 식으로MyRunnable
지역내부 클래스를 사용하고 있다.MyRunnable
을 사용하려면 이 클래스를 직접 생성하는 것이 아니라getRunnable()
메소드 호출을 통해 생성된 객체를 반환 받아야한다.
지역내부 클래스에서 지역 변수의 유효성
- 변수의 유효성에 대해서 살펴보자. 지역변수는 메소드가 호출 될 때 스택 메모리에 생성되고 끝나면 사라진다. 그런데 지역 내부 클래스에 포함된 getRunnable()메소드를 호출해도 run()이 정상적으로 호출된다.
- 이를 통해 메소드 호출이 끝나고 스택 메모리에 지워진 변수를 또 참조할 수 있다는 것을 알 수 있다. final 키워드가 자동으로 추가되기 때문이다.
- 그래서 지역내부 클래스의 메소드를 바꾸려고 하면 오류가 발생한다.
익명 내부 클래스
- 위의 지역내부 클래스에서 MyRunnable을 선언했지만 사용하는곳은 맨 마지막 리턴값에서 생성할 뿐이다.
- 클래스 이름을 빼고 클래스를 바로 생성하는 방법과 인터페이스나 추상 클래스형 변수를 선언하고 클래스를 생성해 개입하는 방법 두가지가 있다.
class Outer2 {
Runnable getRunnable(int i) {
int num = 100;
return new Runnable() {
@Override
public void run() {
System.out.println(i);
System.out.println(num);
}
};
}
Runnable runner = new Runnable() {
@Override
public void run() {
System.out.println("Anonymous class variable");
}
};
}
public class AnonymousInnerTest {
public static void main(String[] args) {
Outer2 out = new Outer2();
Runnable runnable = out.getRunnable(10);
runnable.run();
out.runner.run();
}
}
-아래는 실행결과다
10
100
Anonymous class variable
- 익명내부 클래스는 단 하나의 인터페이스 또는 단 한나의 추상 클래스를 바로 생성 할 수 있다.
- Runnable인터페이스를 생성할수 있으려면 인터페이스 몸체가 필요하다. 위 코드를 보면 반드시 구현해야 하는 run()메소드가 포함된것을 알수 있다.
- 그리고 마지막에 ;를 사용하여 익명내부클래스가 끝났다는것을 알려준다.
- 인터페이스나 추상클래스 자료형으로 변수 선언을 한 다음 익명 내부 클래스를 생성해 다입하는 방법으로도 사용할 수 있다.