자바 자바학원 java java학원 scjp scjp학원 자바자격증 java자격증 scjp자격증 jsp ejb 자바프로그래밍 웹프로그래밍
[자바/SCJP/JAVA]내부 클래스(Inner Class)
클래스 안에 클래스를 정의할 수도 있는데 이런 클래스를 내부 클래스(Inner Class)라고 한다. 네 종류의 내부 클래스가 있다.
종류 |
형태 |
정적 클래스(Static Calss) |
클래스의 멤버로 static 클래스가 있는 경우 |
멤버 클래스(Member Class) |
클래스의 멤버로 클래스가 있는 경우 |
지역 클래스(Local Class) |
메소드 내에 클래스가 있는 경우 |
익명 클래스(Anonymous Class) |
이름이 없는 클래스 |
[표 10-1] 내부 클래스
정적 클래스(Static Calss)
static 클래스는 static 변수와 static 메소드의 이해 없이는 상당히 어려울 것이다. 독자가 생각하기에 static에 대한 이해가 부족하다고 생각되면 5장을 한번 더 보는 것도 좋을 것이다.
static 클래스를 다른 말로 중첩 클래스(Nested Class)라고 한다. 대부분의 자바 책에서 그렇게 부르고 있다. 하지만 필자의 경험으로 중첩이라는 말 자체도 어려웠고 static 클래스와 결부시켜 이해하는 것도 어려웠기 때문에 그냥 있는 그대로 static 클래스라고 부를 것이다.
짚어두기 |
|
static과 dynamic 영어 사전에서 찾아보면 각각 '정적인', '동적인'이라는 말로 서로 반대 말이다. static 변수는 메모리에 한번 자리를 잡으면 프로그램이 끝나기 전까지 결코 사라지지 않는다. 즉 static 변수는 정적(靜的)이다. 하지만 멤버 변수는 객체가 생성되면 생겼다가 객체가 소멸될 때 사라진다. 즉 일반 멤버 변수는 동적(動的)이다. |
다음 코드를 보면 클래스 내부에 static 클래스가 존재한다.
class Outer{ static class Inner{ } } |
Outer 클래스 안에 Inner 클래스가 있다. 이놈들을 어떻게 해석을 해야할까? Inner는 Outer안에 있으므로 Outer의 멤버라고 생각하자. 근데 Inner는 static 클래스이므로 static 멤버이다. static 멤버는 객체를 만들지 않고도 사용할 수 있으므로 Outer의 객체 없이도 Inner를 참조할 수 있다. 다른 말로 Outer의 객체 없이도 Inner의 객체를 만들 수 있다. Outer 클래스 외부에서 Inner 객체를 만들면 아래와 같다.
Outer.Inner ob=new Outer.Inner(); |
static 변수를 클래스 외부에서 참조하는 것과 비슷해 보인다.
Outer 클래스 내부에서 Inner 객체를 만들면 아래와 같다.
Inner ob=new Inner(); |
클래스 내부에서 Inner를 참조하기 때문에 클래스이름을 생략해도 된다.
static 메소드에서 static 변수나 메소드를 참조할 수 있는 것과 같이 static 클래스인 Inner도 Outer의 static 변수나 메소드를 참조할 수 있다. 하지만 일반 멤버는 참조할 수 없다.
class Outer{ static int a=10; int b=20; static void f(){...} static class Inner{ void g(){ int d=a; // static 변수 f(); // static 메소드 // b=100; 일반 멤버는 참조 불가 } } } |
이제 예제를 보고 분석해 보자.
Static1.java |
|
class Outer{
static int a=10;
int b=20;
static void f(){
System.out.println("hi~~");
}
static class Inner{
int c=30;
public void g(){
f(); // Outer의 static 메소드 호출
System.out.println(a+" "+c); // Outer의 static 변수 참조
}
}
}
public class Static1{
public static void main(String[] args){
Outer.Inner ob=new Outer.Inner();
ob.g();
}
}
출력 결과 |
|
hi~~
10 30
위 예제와 같이 static 클래스는 Outer의 멤버지만 Outer의 객체 없이도 Inner 클래스의 객체를 만들 수 있다.
멤버 클래스(Member Class)
멤버 클래스의 모양은 다음과 같다.
class Outer{ class Inner{ } } |
Inner가 Outer의 멤버로 정의된 경우이다. 객체를 만들어야만 멤버 변수를 사용할 수 있는 것과 같이, Outer의 객체가 있어야 Inner의 객체를 만들 수 있다.
Outer out=new Outer(); // Outer의 객체 Outer.Inner in=out.new Inner(); // Inner의 객체는 Outer의 객체를 통해서 |
모양이 이상하다. 하지만 곰곰이 생각해보면 이해될 것이다.
멤버 메소드에서 모든 멤버를 참조할 수 있듯이, Inner 클래스는 Outer의 모든 멤버를 참조할 수 있다.
class Outer{ static int a=10; int b=20; static void f(){...} void g(){...} class Inner{ void h(){ int d=a; // static 변수 f(); // static 메소드 b=100; // 멤버 변수 g(); // 멤버 메소드 } } } |
아래 예제를 실행해보고 분석해 보자.
Member1.java |
|
class Outer{
static int a=10;
int b=20;
void f(){
System.out.println("hi~~");
}
class Inner{
int c=30;
public void g(){
b=100;
f();
System.out.println(a+" "+c);
}
}
}
public class Member1{
public static void main(String[] args){
Outer out=new Outer();
Outer.Inner in=out.new Inner();
in.g();
System.out.println(out.b);
}
}
출력 결과 |
|
hi~~
10 30
100
Outer 클래스의 멤버 메소드에서 Inner 클래스의 객체를 만들 수도 있다. 이 때는 Outer의 객체가 이미 만들어진 상태이므로 다음 예제의 x1행과 같이 하면 된다.
Member2.java |
|
class Outer{
void f(){
Inner in=new Inner(); // x1, 이미 Outer의 객체가 만들어졌다.
System.out.println(in.a);
}
class Inner{
int a=100;
}
}
public class Member2{
public static void main(String[] args){
Outer out=new Outer(); // 여기서 Outer의 객체가 만들어짐.
out.f();
}
}
출력 결과 |
|
100
외부 클래스의 객체가 있어야만 멤버 클래스를 참조할 수 있기 때문에 멤버 클래스 내의 멤버는 static이 될 수 없다. 하지만 상수는 가능하다.
class Outer{ class Inner{ int a; // 가능 void f(){...} // 가능 static final int b=10; // 가능 static int c; // 에러 static void g(){...} // 에러 } } |
다음 코드와 같이 Outer 클래스의 멤버와 Inner 클래스의 멤버의 이름이 같은 경우가 있다.
class Outer{ int a; // x1 class Inner{ static final int a=20; // x2 void f(){ int b=a; // x3 int c=this.a // x4 int d=Outer.this.a // x5 } } } |
x3행이나 x4행의 a나 this.a는 x2행의 a를 가리킨다. 외부클래스의 멤버를 참조하려면 x5행과 같이 한다. Outer.this는 Outer의 객체의 레퍼런스이다. Outer 객체의 this와 Inner 객체의 this를 구분하기 위하여 위와 같이 하는 것이다.
다음 예제로 확인해보자.
Member3.java |
|
class Outer{
int a=10; // x1
class Inner{
static final int a=100; // x2
void f(){
System.out.println(this.a); // this.a는 x2행의 a를 가리킴
System.out.println(Outer.this.a); // Outer.this.a는 x1행의 a를 가리킴
}
}
}
public class Member3{
public static void main(String[] args){
Outer out=new Outer();
Outer.Inner in=out.new Inner();
in.f();
}
}
출력 결과 |
|
100
10
지역 클래스(Local Class)
지역 클래스는 지역 변수와 같이 메소드 내부에서 정의된 클래스이다.
void f(){ class Inner{ } } |
메소드 f() 내부에 Inner 클래스가 정의되어 있다. 정말이지 묘한 모양을 하고 있다. 하지만 지역 변수와 같다고 생각하면 된다. 지역 변수는 메소드 내부에서만 사용할 수 있고 메소드의 실행이 끝나면 사라진다. 지역 클래스도 마찬가지로 메소드 내부에서만 객체를 만들 수 있고 메소드의 실행이 끝나면 클래스가 메모리에서 사라진다.
void f(){ class Inner{ // x1 void hi(){ System.out.println("안뇽~~~"); } } Inner in=new Inner(); // x2, 메소드 내부에서 객체를 생성 in.hi(); // x3 } // x4 |
메소드 f()를 호출하면 x1행에서 Inner 클래스가 정의된다. x2행에서 객체를 만들고 사용한다. x4행에서 메소드 f()의 실행이 종료되면 메소드 내부에서 정의된 모든 것들이 소멸된다. 즉, Inner 클래스와 in의 객체가 메모리에서 제거된다. 메소드를 호출할 때마다 이 같은 과정이 반복된다.
지역 클래스는 자신을 포함하는 클래스(enclosing class)의 모든 멤버를 참조할 수 있다.
Local1.java |
|
public class Local1{
int a=10;
void f(){
class Inner{
int c=20;
void hi(){
System.out.println(a);
System.out.println(c);
}
}
Inner in=new Inner();
in.hi();
}
public static void main(String[] args){
Local1 local=new Local1();
local.f();
}
}
출력 결과 |
|
10
20
지역 클래스는 자신을 포함하는 메소드의 지역 변수나 매개 변수 중에 final만 참조할 수 있다.
다음 예제를 해보자.
Local2.java |
|
public class Local2{
void f(final int a, int b){
int c=30;
final int d=40;
class Inner{
void hi(){
System.out.println(a);
// System.out.println(b); 에러, final이 아니다.
// System.out.println(c); 에러
System.out.println(d);
}
}
Inner in=new Inner();
in.hi();
}
public static void main(String[] args){
Local2 local=new Local2();
local.f(10,20);
}
}
출력 결과 |
|
10
40
익명 클래스(Anonymous Class)
interface로 정의된 Inter가 다음과 같이 정의되어 있다고 가정하자.
interface Inter{ public void f(); } |
Inter를 구현하는 클래스를 만들고자 한다. 그런데 이 클래스로부터 단 하나의 객체가 만들어진다면 다음과 같이 할 수 있다.
Inter ob = new Inter(){★}; |
★가 있는 자리에 Inter를 구현하면 되는데, '{★}'를 익명 클래스(이름 없는 클래스)라고 한다. 이렇게 클래스를 정의하면 이 클래스는 레퍼런스 변수 ob만 사용할 수 있다.
Inter ob=new Inter(){ public void f(){ System.out.println("안녕~~~"); } }; // 세미콜론을 빼먹지 말자. |
위 코드를 가만히 보자. 클래스를 정의하고 객체를 만들지만 마치 객체를 정의하는 것처럼 보인다. 익명 클래스는 오직 하나의 객체를 위해서 정의하는 것이다.
다음 예제를 보고 분석해보자.
Anony1.java |
|
interface Inter{
public void hi();
}
public class Anony1{
public static void main(String[] args){
Inter ob1=new Inter(){ // Inner를 구현하는 객체를 생성한다.
public void hi(){
System.out.println("안녕~~~");
}
};
Inter ob2=new Inter(){ // Inner를 구현하는 객체를 생성한다.
public void hi(){
System.out.println("hello~~~");
}
};
ob1.hi();
ob2.hi();
}
}
출력 결과 |
|
안녕~~~
hello~~~
이름을 가진 클래스를 정의하는 것보다 익명 클래스를 사용하는 것이 코드가 적다.
인터페이스뿐만 아니라 클래스를 상속하는 익명 클래스도 만들 수 있다.
Anony2.java |
|
class Anony2_1{
public void hi(){
System.out.println("hi?");
}
public void hello(){
System.out.println("hello?");
}
}
public class Anony2{
public static void main(String[] args){
Anony2_1 ob=new Anony2_1(){ // Anony2_1을 상속하는 클래스의 객체
public void hi(){
System.out.println("안녕?");
}
};
ob.hi();
ob.hello();
}
}
출력 결과 |
|
안녕?
hello?
|
연습 문제 |
|
1. static 클래스와 멤버 클래스의 차이점은 무엇인가?
2. 멤버 클래스를 이용하는 목적이 무엇인지 생각해보자.
3. 익명 클래스의 장점은 무엇인가?
4. 다음의 Shape(도형) 인터페이스를 구현하는 클래스의 객체를 만들되 익명 클래스로 작성해보자.
public Interface Shape{
// 좌표(x, y)가 도형 내부에 있는 점이면 true를, 아니면 false를 반환한다.
public boolean contains(int x, int y);
// 영역(x, y, w, h)가 도형과 교차하면 true를 아니면 false를 반환한다.
// 영역이란 외쪽 상단의 좌표가 (x, y)이고 너비가 w, 높이가 h인 사각형이다.
public boolean intersects(double x, double y, double w, double h);
}
// 익명 클래스를 이용한 Shape 객체 만들기
Shape 사각형 = new Shape(){
...
};
Shape 원 = new Shape(){
...
};
자바 자바학원 java java학원 scjp scjp학원 자바자격증 java자격증 scjp자격증 jsp ejb 자바프로그래밍 웹프로그래밍