중첩클래스
안드로이드 프로그래밍을 하면서, 클래스 내부에 다른 클래스를 만드는 형태의 코드를 종종 짜게 되었다. 관련 레퍼런스나 다른 사람들이 작성한 코드를 복붙하는 수준으로만 했었는데, 문뜩 클래스 내부에 다른 클래스를 구현하는 이유와 그 종류가 궁금해 공부해 보았다.
신용권, 『이것이 자바다』, 한빛미디어(2015), 9장 을 이용해 공부한 내용을 정리해보았다. 중간중간 내 생각도 추가되어있다.
중첩클래스
클래스 내부에 선언한 클래스를 중첩클래스라고 한다. 객체지향 프로그래밍에서 각 클래스들은 서로 관계를 맺고 메소드와 필드를 사용해 상호작용을 한다. 그러다보면 어떤 클래스는 다른 여러 클래스와 관계를 맺기도 하고, 어떤 클래스는 하나의 클래스와만 관계를 맺기도 한다. 설계를 하는 입장에서, 그 복잡성을 줄이려면, 특정클래스와만 관계를 맺는 클래스를 다른 관계없는 클래스들로부터 숨길 필요가 있다. 이 때 숨기는 방법의 일환으로 중첩클래스를 사용한다. (반대로 말하면, 여러 클래스와 관계를 맺을 필요가 있다면 중첩클래스로 선언하면 안된다고 할 수 있다.) 중첩클래스를 사용하면, 두 클래들 사이에서는 서로의 멤버에 쉽게 접근할 수 있고 외부에는 감춤으로써 코드의 복잡성을 줄일 수 있다.
마찬가지로 인터페이스도 클래스 내부에 만들 수 있는데 이를 중첩인터페이스라고 한다. 이를 사용하는 이유는 인터페이스의 필요성 + 특정 클래스와만 관계를 맺는 상황 일 때 사용한다(대표적으로 UI 프로그래밍이 있다)
이 포스팅에서 바깥클래스란 중첩클래스를 가지고있는 클래스를 말하고, 외부 클래스란 전혀 다른 클래스를 말한다. 코드로 표현하면 아래와 같다.
package test;
public class A { // 바깥클래스
public int field1;
public void method1(){
...
}
public class B { // 내부클래스
public int field2;
public void method2(){
...
}
}
}
---------------------------------------------
package test2;
import test.*;
public class Main { // 외부클래스
public static void main(String[] args){
A a = new A();
}
}
중첩클래스의 종류
중첩클래스는 그 선언 위치에 따라 크게 2가지로 나눌 수 있다. 하나는 클래스의 멤버자리에 선언하는 멤버클래스고, 다른 하나는 특정 메소드 내부에 선언하는 로컬클래스이다. 멤버클래스는 바깥클래스가 사용중이라면 언제든 재사용이 가능하지만, 로컬클래스는 그 메소드가 실행시에만 사용되고, 메소드가 종료되면 사라진다.
멤버클래스는 다시 인스턴스멤버클래스와 정적멤버클래스로 나뉜다. 정적멤버클래스는 클래스 선언문 앞에 static 키워드가 붙는다.
선언 위치에 따른 분류 | 선언 위치 및 방법 | 설명 | |
멤버 클래스 | 인스턴스 멤버 클래스 | 필드 자리에 클래스 선언방식대로 | 외부 클래스의 객체를 통해서만 사용할 수 있는 중첩클래스 |
정적 멤버 클래스 | 필드 자리에 static 키워드 뒤에 클래스 선언방식대로 |
외부 클래스로 바로 접근할 수 있는 중첩클래스 |
|
로컬 클래스 | 메소드 내부에 클래스 선언방식대로 | 해당 메소드가 실행될 때만 사용가능한 중첩클래스 |
1. 인스턴스 멤버 클래스
인스턴스 멤버 클래스는 static 키워드 없이 선언된 내부클래스를 의미한다. 인스턴스 멤버 클래스는 정적필드와 정적메소드를 가질 수 없다.
만일 외부 클래스 C 에서 A의 인스턴스 멤버클래스 B의 객체를 생성하려면 우선 C에서 A의 객체를 생성하고 이를 통해서 B의 객체를 선언해야 한다.
A a = new A();
A.B b = new A.B();
b.field = 1;
b.method();
2. 정적 멤버 클래스
정적 멤버 클래스는 static 키워드로 선언된 클래스를 말한다. 정적 멤버 클래스는 모든 종류의 필드와 메소드를 선언할 수 있다.
만일 외부 클래스 C 에서 A의 정적 멤버클래스 B의 객체를 생성하기 위해 A의 객체를 생성할 필요 없이 다음과 같이 하면 된다.
A.B b = new A.B();
b.field = 1; // 인스턴스 필드 사용
b.method(); // 인스턴스 메소드 사용
A.B.static_field = 2; // 정적필드 사용
A.B.static_method(); //정적 메소드 사용
3. 외부 클래스에서 내부클래스를 사용하는 것이 좋은가?
바로 위에서 외부 클래스에서 바깥클래스의 내부클래스를 사용하는 방법을 설명했다. 하지만 개인적으로 이렇게 사용하는 것은 지양해야 한다고 생각한다.
C에서 B의 객체를 선언하여 사용한다는 것은 C와 B 사이에 관계가 만들어지는 것을 의미한다. 애초에 중첩클래스는 바깥클래스와 내부클래스 사이에만 관계가 있을 때 사용하는 방식이므로 C에서 B를 사용한다는 것은 B를 A의 내부클래스로 선언할 이유가 없어진다. 따라서 이 경우 B를 A의 내부클래스가 아닌 독립적인 클래스로 선언하는것이 좋다고 본다.
하지만 이게 가능하기 때문에 알아둘 필요는 있다. 당장 설계를 바꿀 시간이 없으면 이렇게라도 해야 할 테니까.
4.로컬클래스
로컬클래스는 메소드 내에서 선언한 클래스를 말한다. 로컬클래스는 메소드 내부에서 선언되었으므로 해당 메소드가 동작할 때만 사용할 수 있다. 따라서 외부클래스에서 접근 자체가 성립될 수 없다. 그래서 로컬클래스에서는 public, private 등의 접근제한자를 사용할 수 없다. 로컬 클래스에서는 정적 필드와 메소드를 사용할 수 없다.
중첩 클래스의 접근제한
1. 바깥클래스에서 내부클래스의 객체를 사용할 때
바깥클래스에서 멤버클래스의 객체를 사용할 때 멤버클래스의 종류에 따라 제한이 생긴다.
정적멤버클래스는 바깥클래스의 모든 필드와 메소드에서 객체를 생성하거나 사용할 수 있지만, 인스턴스멤버클래스는 바깥클래스의 객체가 존재해야 사용할 수 있고, static 필드나 메소드는 객체생성 없이 사용할 수 있는 것들이므로 바깥클래스에서 인스턴트멤버클래스를 사용할 수 없다.
public class A {
B field1 = new B();
C field2 = new C();
void method1(){
B var1 = new B();
C var2 = new C();
}
//static B field3 = new B();
//바깥클래스의 정적 필드에서 인스턴스멤버클래스의 객체를 사용할 수 없다.
static C field4 = new C();
static void method2(){
//B var1 = new B();
//바깥클래스의 정적 메소드에서 인스턴스멤버클래스의 객체를 사용할 수 없다.
C var2 = new C();
}
class B{}
static class C{}
}
2. 멤버클래스에서 바깥클래스의 필드와 메소드를 사용할 때
인스턴스 멤버클래스의 내부에선 바깥클래스의 모든 필드와 메소드에 접근이 가능하지만, 정적 멤버클래스에서는 바깥클래스의 정적필드와 정적 메소드에만 접근이 가능하다. 당연한게, 인스턴스 멤버클래스의 객체는 바깥클래스의 객체를 통해 생성이 가능하므로 바깥클래스의 모든 필드와 메소드에 접근이 가능하지만 정적멤버클래스는 바깥클래스의 객체가 존재함을 보증하지 못하니 바깥클래스의 정적필드와 메소드만 사용할 수 있는 것이다.
3. 로컬클래스에서 바깥클래스의 필드와 메소드를 사용할 때
로컬클래스에서는 바깥클래스의 필드나 메소드를 제한없이 사용할 수 있다. 바깥클래스의 객체가 존재함이 확실하기 때문이다.
그러나, 로컬클래스를 가지는 메소드의 매개변수나 로컬변수를 로컬클래스에서 사용 할 때에는 제한이 있다. 매개변수나 로컬변수는 스택에 저장되고, 객체는 힙에 저장되며, 스택은 메소드가 종료되면 사라지므로 문제가 생긴다. 물론 로컬클래스의 객체는 메소드가 종료되면 접근이 불가능하기 때문에 GC 처리 대상이겠지만 그 시간차이가 존재하고 쓰레드 등 비동기 상황이라면 확실히 문제가 될 수 있다. 이 문제를 해결하기 위해 컴파일 시 로컬클래스에서 사용하는 매개변수나 로컬변수의 값을 클래스 내부에 복사해서 사용한다. 이렇게 되면 로컬클래스에 복사해둔 값과 실제 로컬변수의 값이 달라질 수 있기 때문에 final 로 선언해 문제를 막는다.
결론적으로 말하면 자바 7 까지는 로컬클래스에서 메소드의 로컬변수나 매개변수가 final인 것만 사용가능하고, 그 이후부터는 유사 final (final 키워드는없지만 코드내에서 수정이 전혀 없는 변수) 도 사용 가능하다.
다음글
중첩클래스 2
이전글 2021/02/28 - [Java] - 중첩클래스 중첩클래스 안드로이드 프로그래밍을 하면서, 클래스 내부에 다른 클래스를 만드는 형태의 코드를 종종 짜게 되었다. 관련 레퍼런스나 다른 사람들이 작성
robinjoon98.tistory.com