이번 포스트는 중첩 클래스에 대해 정리하고자 한다.
먼저 중첩 클래스는 두가지로 나눌 수 있다.
- 스태틱 클래스
- 내부 클래스
중첩 클래스의 사용 이유는 클래스들의 논리적인 그룹을 model 내 객체에서 나타낼 때 사용한다.
중첩 클래스를 그룹 내부에 선언하기 때문에 좋은 가독성과 유지보수성을 가진다.
더 향상된 캡슐화의 형태이다.
class OutderClass{
@Builder
@ToString
static class User{
public String name;
public String mail;
}
}
public class StaticClass {
public void test(){
//외부에서 내부 static 중첩 클래스 선언
OuterClass.User user = new OuterClass.User.UserBuilder().name("test2").mail("google.com").build();
}
}
정적 중첩클래스(스태틱 클래스)는 내부 클래스와 구조가 굉장히 비슷하지만,
다음과 같은 차이점이 존재한다.
위 코드의 StaticClass의 test()메서드를 보면 정적 중첩클래스를 외부에서 생성하는 방법이다.
내부 클래스는 아래와 같이 3가지로 나눌 수 있다.
- 내부 클래스
- 로컬 클래스
- 익명 내부 클래스
public class OuterClass {
private String outer1;
class InnerClass{
public void test(){
System.out.println(outer1);
System.out.println("내부 클래스 호출입니다.");
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.outer1 = "외부 클래스 변수1"; //외부 클래스 멤버변수 초기화
OuterClass.InnerClass innerClass = outerClass.new InnerClass(); //내부 클래스를 외부에서 객체 생성
innerClass.test();
}
}
출력결과
외부 클래스 변수1
내부 클래스 호출입니다.
내부 클래스는 내부 클래스를 감싸고 있는 외부 클래스의 멤버들을 자유롭게 접근할 수 있다.
단일 클래스 내에서만 사용되는 클래스의 경우는 내부 클래스로 선언하여 사용하는 것이 좋다.
그 이유는
public class OuterClass {
public void method(final User user){
class LocalClass{
public void localClassTest(){
System.out.println("로컬 클래스" + user.toString());
}
}
new LocalClass().localClassTest();
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
User user = new User.UserBuilder().name("test").mail("naver.com").build();
outerClass.method(user);
}
}
출력결과
로컬 클래스OuterClass.User(name=test, mail=naver.com)
내부 익명 클래스OuterClass.User(name=test, mail=naver.com)
로컬 클래스는 메서드 안에 선언된 클래스로 메서드 안에서만 사용되는 클래스는 위와 같이 선언하여 사용할 수 있다.
로컬 클래스의 장점은 메서드 파라미터 및 지역 변수에 자유롭게 접근이 가능하다.
public class OuterClass {
public void method(final User user){
TestInterface testInterface = new TestInterface(){
@Override
public void implementsMethod() {
System.out.println("내부 익명 클래스" + user.toString());
}
};
//람다식 표기
//TestInterface testInterface = () -> System.out.println("내부 익명 클래스" + user.toString());
testInterface.implementsMethod();
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
User user2 = User.builder().name("test2").mail("google.com").build();
outerClass.method(user2);
}
}
출력결과
로컬 클래스OuterClass.User(name=test2, mail=google.com)
내부 익명 클래스OuterClass.User(name=test2, mail=google.com)
마지막으로 익명 내부 클래스는 타입이나 이름이 없기 때문에, 상속이나 구현을 할 때, 인터페이스나 추상 클래스의 타입으로 선언하여 사용한다.
익명 클래슨느 인스턴스 이름이 없기 때문에, new와 동시에 부모 클래스를 상속받아 내부에서 오버라이딩하여 사용.
익명 내부 클래스의 변수나 메서드는 익명 클래스 내부에서만 사용이 가능하다.
또, 익명 클래스에서 외부 자원을 사용할 때, 사용할 자원은 반드시 final로 선언되어야만 한다.
TestInterface testInterface = () -> System.out.println("내부 익명 클래스" + user.toString());
처럼 람다식 표기로 사용할 수도 있다.
public class StaticClass {
public void test(){
//외부에서 내부 static 중첩 클래스 선언
OuterClass.User user = new OuterClass.User.UserBuilder().name("test2").mail("google.com").build();
}
}
public class OuterClass {
private String outer1;
@Builder
@ToString
static class User{
public String name;
public String mail;
}
class InnerClass{
public void test(){
System.out.println(outer1);
System.out.println("내부 클래스 호출입니다.");
}
}
public void method(final User user){
class LocalClass{
public void localClassTest(){
System.out.println("로컬 클래스" + user.toString());
}
}
new LocalClass().localClassTest();
TestInterface testInterface = new TestInterface(){
@Override
public void implementsMethod() {
System.out.println("내부 익명 클래스" + user.toString());
}
};
testInterface.implementsMethod();
}
public interface TestInterface{
void implementsMethod();
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.outer1 = "외부 클래스 변수1";
//내부 클래스
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.test();
User user = new User.UserBuilder().name("test").mail("naver.com").build();
outerClass.method(user);
User user2 = User.builder().name("test2").mail("google.com").build();
outerClass.method(user2);
}
}