내부 클래스 Inner Class = 이너 클래스, 중첩 클래스
내부 클래스는 클래스나 인터페이스 내에서 선언된 클래스를 말한다. 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용할 수 있으며 클래스가 여러 클래스와 관계를 맺는 경우가 아닌, 특정 클래스와 관계를 맺는 경우에 내부 클래스로 선언하는 것을 권장한다.
내부 클래스는 선언 위치에 따라 멤버 내부 클래스, 지역 내부 클래스, 익명 내부 클래스로 나뉠 수 있다. 클래스의 멤버로서 선언되는 클래스를 멤버 내부 클래스, 메서드 내부에서 선언되는 클래스를 지역 내부 클래스, new 연산자로 객체 생성과 동시에 선언되는 익명 내부 클래스라고 한다.
멤버 내부 클래스 - 인스턴스 멤버 클래스
static 키워드 없이 클래스 내부에 선언된 클래스이다. 클래스의 멤버로 인스턴스 필드와 메서드만 선언이 가능하고, 정적 멤버는 가질 수 없다.
public class Outer { //외부 클래스 선언
static int a = 10;
int b = 20;
private int c = 30;
void outerInstanceMethod(){
System.out.println("외부 클래스의 인스턴스 메서드");
}
static void outerStaticMethod(){
System.out.println("외부 클래스의 정적 메서드");
}
class InstanceInner{
//static int d = 40; 인스턴스 멤버 클래스 안에서 static 사용 불가
int e = 50;
private int f = 60;
InstanceInner(){}
InstanceInner(int num1, int num2, int num3){
a = num1; //외부 클래스의 필드를 불러오기 위해서 this.를 사용하지 않는다
c = num2;
this.f = num3; //내부 클래스 자신의 필드를 불러오기 위해 this.를 사용한다
}
void instanceMethod(){
System.out.println("인스턴스 멤버 클래스의 인스턴스 메서드");
System.out.printf("%d, %d, %d, %d, %d \n", a, b, c, e, f);
//외부 클래스의 필드를 제한 없이 사용 가능
outerStaticMethod();
outerInstanceMethod();
}
/*
static void staticMethod(){
System.out.println("인스턴스 멤버 클래스 내부에 static 사용 불가");
}
*/
}
}
Outer 클래스 외부에서 인스턴스 멤버 클래스의 멤버를 사용하기 위해서는 먼저 Outer(외부 클래스)의 객체를 생성하고, 이어서 InstanceInner(내부 클래스)의 객체를 생성해주어야 한다.
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
//외부 클래스의 객체 생성
Outer.InstanceInner instanceInner = outer.new InstanceInner();
//외부 클래스를 통해 내부 클래스를 불러와 내부 클래스의 객체 생성
instanceInner.instanceMethod();
//내부 클래스의 메서드 호출
}
}
출력
인스턴스 멤버 클래스의 인스턴스 메서드
10, 20, 30, 50, 60
외부 클래스의 정적 메서드
외부 클래스의 인스턴스 메서드
헷갈리기 쉬운)
인스턴스 멤버 클래스의 경우 내부 클래스 자신의 멤버로서 static 필드나 메서드를 선언할 수 없을 뿐, 외부 클래스에서 선언된 static 필드나 메서드는 객체 생성 없이 바로 사용할 수 있다는 것에 주의하자.
멤버 내부 클래스 - 정적 멤버 클래스
static 키워드로 클래스 내부에 선언된 클래스이다. 클래스의 멤버로 모든 종류의 필드와 메서드를 제한 없이 사용할 수 있다.
public class Outer { //외부 클래스 선언
static int a = 10;
int b = 20;
private int c = 30;
void outerInstanceMethod(){
System.out.println("외부 클래스의 인스턴스 메서드");
}
static void outerStaticMethod(){
System.out.println("외부 클래스의 정적 메서드");
}
static class StaticInner{ //정적 멤버 클래스 선언
static int d = 40;
int e = 50;
private int f = 60;
//외부 클래스의 모든 필드를 제한 없이 사용 가능하다
void instanceMethod(){
System.out.println("정적 멤버 클래스의 인스턴스 메서드");
Outer outer = new Outer();
System.out.printf("%d, %d\n", outer.b, outer.c);
//객체 생성 후 static으로 선언되지 않은 b, c 필드를 사용할 수 있다
outer.outerInstanceMethod();
//외부 클래스의 인스턴스 메서드 외부 클래스의 객체 생성 후 사용할 수 있다
}
void staticMethod(){
System.out.println("정적 멤버 클래스의 정적 메서드");
System.out.printf("%d, %d, %d, %d\n", a, d, e, f);
//외부 클래스에서 정적으로 선언된 필드는 바로 사용할 수 있다
Outer.outerStaticMethod();
//외부 클래스에서 정적으로 선언된 메서드 역시 바로 사용 가능하다
}
}
}
Outer 클래스 외부에서 정적 멤버 클래스의 멤버를 사용하기 위해서는 Outer(외부 클래스)의 객체를 생성해주지 않고 바로 StaticInner(정적 멤버 클래스)의 객체를 생성해서 사용 가능하다.
package innerclass.static_inner.thisisjava;
public class Main {
public static void main(String[] args) {
Outer.StaticInner staticInner = new Outer.StaticInner();
staticInner.instanceMethod();
staticInner.staticMethod();
}
}
출력
//staticInner.instanceMethod();
정적 멤버 클래스의 인스턴스 메서드
20, 30
외부 클래스의 인스턴스 메서드
//staticInner.staticMethod();
정적 멤버 클래스의 정적 메서드
10, 40, 50, 60
외부 클래스의 정적 메서드
인스턴스 멤버 클래스 vs. 정적 멤버 클래스
인스턴스 멤버 클래스 |
정적 멤버 클래스 |
내부 클래스 자신의 멤버로 static 사용 가능 여부 |
|
자신의 멤버로 static을 가질 수 없다 |
자신의 멤버로 static을 제한없이 사용 가능하다 |
내부 클래스에서 외부 클래스의 필드에 값을 넣어줄 때 |
|
외부 클래스의 필드에 this. 키워드를 붙이지 않고 바로 a = num; 형식으로 사용할 수 있다 this. 키워드를 사용하는 경우는 내부 클래스 자신의 필드에 값을 대입하고자 할 때 사용한다 |
|
외부에서 내부 클래스에 접근하고자 하는 경우 |
|
외부 클래스의 객체를 먼저 생성하고, 인스턴스 내부 클래스의 객체를 생성해 사용한다 |
외부 클래스에서 바로 정적 내부 클래스를 호출하고(.) 객체를 생성해 사용한다 |
외부 클래스에서 선언된 static 필드와 메서드 |
|
객체 생성 없이 호출해 사용 가능하다 |
|
외부 클래스에서 선언된 인스턴스 필드와 메서드 |
|
객체 생성 없이 바로 불러와 사용할 수 있다 |
외부 클래스의 객체를 먼저 생성하지 않으면 사용할 수 없다 |
지역 내부 클래스
메서드 내부에서 선언된 클래스를 말한다. 메서드 내부에서만 사용되는 클래스이기 때문에 접근 제한의 의미가 없으므로 접근 제한자는 붙일 수 없고 static 키워드 역시 사용할 수 없다. 즉, 지역 내부 클래스의 멤버로는 인스턴스 필드와 메서드만 선언 가능하다.
public class Outer {
static int a = 10;
int b = 20;
private int c = 30;
void outerInstanceMethod(){
System.out.println("외부 클래스의 인스턴스 메서드");
class LocalInner{
//static int d = 40; 지역 내부 클래스에서 static 사용 불가
int e = 50;
private int f = 60;
void localInstanceMethod(){
System.out.println("지역 내부 클래스의 인스턴스 메서드");
System.out.printf("%d, %d, %d, %d, %d \n", a, b, c, e, f);
}
/*
static localStaticMethod(){
System.out.println("지역 내부 클래스에서 정적 메서드는 사용 불가");
}
*/
}
LocalInner localInner = new LocalInner();
//메서드 내에서 지역 내부 클래스의 객체를 생성
localInner.localInstanceMethod();
//외부 클래스의 메서드 실행시 내부 클래스의 메서드까지 호출된다
}
}
외부 클래스의 메서드 블록 안에서 지역 내부 클래스의 객체를 생성하고, 메서드를 호출했다. 따라서 외부에서 외부 클래스의 객체를 생성하고 외부 클래스의 메서드를 호출함으로써 지역 내부 클래스의 메서드까지 호출된다.
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.outerInstanceMethod();
}
}
출력
외부 클래스의 인스턴스 메서드
지역 내부 클래스의 인스턴스 메서드
10, 20, 30, 50, 60
+) 비동기 처리를 위해 스레드 객체를 만들 때 주로 사용한다고 한다.
익명 내부 클래스
익명 내부 클래스는 말 그대로 이름 없이 선언된 클래스이다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 한 개의 객체만을 생성 가능하며 일회용으로 사용된다.
class Demo {
void show(){
System.out.println("Demo class - show()");
}
}
class Test{
static Demo d = new Demo(){ //익명 내부 클래스 선언
void show(){
super.show();
System.out.println("Test class - show()");
}
};
public static void main(String[] args) {
d.show();
}
}
출력
Demo class - show()
Test class - show()
인스턴스 멤버 클래스, 정적 멤버 클래스, 지역 내부 클래스, 익명 내부 클래스를 알아보았다. 다른 듯 보이지만 모든 내부 클래스는 모두 외부 클래스와 연관되어 있다는 공통점을 가진다.
내부 클래스를 사용함으로써 얻을 수 있는 이점
클래스를 논리적으로 그룹화한다
클래스가 여러 클래스와 관계를 맺지 않고 하나의 특정 클래스와만 관계를 맺는다면, 클래스를 새로 작성하는 것이 아닌 내부 클래스로 작성할 수 있다. 이 경우 내부 클래스와 외부 클래스를 함께 관리하는 것이 가능해 편리해질 뿐 아니라, 내부 클래스로 인해 새로운 클래스를 생성하지 않아도 되므로 패키지를 간소화할 수 있다.
기존보다 더 많은 캡슐화의 적용이 가능하다
외부 클래스의 멤버를 private으로 선언하면 외부에서는 getter/setter 메서드를 사용하지 않는 한 접근이 불가능하다. 하지만 이 클래스 안에서 내부 클래스를 생성할 경우, 내부 클래스는 private 멤버들에 대해 아무런 제한 없이 접근이 가능하다. 또한, 외부 클래스의 private 멤버를 사용하면서 자기 자신의 접근 제한 역시 private으로 설정해 외부로부터의 접근을 차단할 수 있다.
이것이 내부 클래스로 더 많은 캡슐화의 적용, 즉 외부로부터의 데이터 보호가 가능한 이유이다.
가독성이 좋고 유지 관리가 쉬운 코드 작성이 가능하다
내부 클래스를 작성하는 경우, 클래스를 따로 작성하는 경우보다 상위 클래스에 더 가깝게 위치하게 된다. 따라서 시각적으로 읽기가 편해질 뿐 아니라 유지보수에 있어 이점을 가진다.
reference.
신용권, 이것이 자바다, 한빛미디어, 2018
'p-languages > java' 카테고리의 다른 글
java/ 배열을 요소로 갖는 리스트에서 배열의 값을 기준으로 오름차순 정렬하기 (0) | 2022.07.23 |
---|---|
java/ 8 또는 12 길이의 int 배열을 받아 전화번호 형식의 String으로 반환하기 (0) | 2022.06.06 |
java/ Stream 중간 처리 메서드 - filtering, mapping, sorting, looping (0) | 2022.05.23 |
java/ String.contains() : 문자열에 특정 부분 문자열이 있는지 확인하는 메서드 (0) | 2022.05.17 |
java/ replace(), replaceAll(), replaceFirst() : 문자열에서 문자 또는 부분 문자열을 교체하는 메서드 (0) | 2022.05.15 |