오늘도 고군분투하는 우리의 키메라! 캬오!
원래는 람다에 대해서만 알아보려고 했는데, 공부하다보니 먼저 Nested Class와 Inner Class에 대해 알아보려고 한다.
다 이해하고 외워버려!
1. 중첩 클래스(Nested class)
우선 inner class를 알기 위해서는 Nested Class에 대해 먼저 알아야 한다.
자바 프로그래밍 언어는 다른 클래스 안에 클래스를 정의할 수 있는데 이러한 클래스는 중첩 클래스(nested class)라고 한다.
class OuterClass {
...
class NestedClass {
...
}
}
여기서 Nested class는 non-static와 static이라는 두 개의 카테고리로 나눌 수 있는데,
Non-static nested Class가 내부 클래스(inner class)라고 불린다.
static 으로 선언된 중첩 클래스(Nested class)는 정적 중첩 클래스(static nested classs)라고 한다.
class OuterClass {
...
class InnerClass {
...
}
static class StaticNestedClass {
...
}
}
중첩 클래스는 닫혀있는 클래스의 한 멤버이다. Inner class는 닫힌 클래스의 다른 멤버들이 private하게 선언되어 있어도 접근할 수 있다.
정리하면 중첩 클래스에서 정적인 클래스가 아니라면 내부클래스이고,
static 이면 정적 중첩 클래스라고 한다. 후...
2. 중첩 클래스는 왜 쓰는가?
공식문서에서는 다음과 같이 설명하고 있다.
한 곳에서만 사용되는 클래스들을 논리적으로 그룹화할 수 있다.
어떤 클래스가 오직 하나의 다른 클래스에서만 유용하게 사용된다면, 해당 클래스를 그 안에 중첩시켜 함께 유지하는 것이 논리적으로 타당하다.
이러한 "헬퍼 클래스(helper class)"들을 중첩하면 해당 패키지를 더 간결하게 만들 수 있다.
캡슐화를 향상시킨다:
예를 들어, 두 개의 최상위 클래스 A와 B가 있다고 하자.
이때 클래스 B가 클래스 A의 멤버에 접근할 필요가 있지만, 그 멤버들은 원래 private로 선언되어야 한다면, B를 A 내부에 숨김으로써 A의 멤버들을 private로 유지하면서도 B가 접근할 수 있게 할 수 있다.
더불어, 클래스 B 자체도 외부로부터 숨길 수 있다.
더 읽기 쉽고 유지보수하기 쉬운 코드가 된다:
작은 클래스를 최상위 클래스 내부에 중첩시키면, 해당 클래스가 사용되는 위치 근처에 코드를 배치할 수 있어 가독성과 유지보수성이 좋아진다.
결국에는 캡슐화를 통한 가독성 향상을 꾀하는게 중첩 클래스의 목적이다!
3. Inner Class?
인스턴스 메소드와 변수처럼, 내부클래스는 외부 클래스의 인스턴스와 연관되어 있고 외부 클래스의 객체 매소드와 필드에 접근할 수 있다. 또 인스턴스와 연관이 있어서 static memeber 를 선언할 수 없다.
그래서 테스트 해봤더니... 되는데?
정말 static member를 선언할 수 없나 실제로 해보니 되는것에 키메라는 놀랐다!
알고 보니 Java 16 이상부터는 로컬 클래스이며, 로컬 클래스가 static 메서드 안에 있다면 가능하다고 한다.
하여간 계속 공식문서를 읽도록 하겠다.
class OuterClass {
...
class InnerClass {
...
}
}
InnerClass의 인스턴스는 OuterClass의 인스턴스 내에서만 존재할 수 있고 외부 클래스의 필드나 메소드에 접근할 수 있다. (너무 당연한 말이긴 하다 긁적긁적)
두 가지 특별한 내부 클래스의 종류가 있는데, Local Class와 Anonymous Class가 있다.
이 글에 이어서 Local Class, Anonymous Class 그리고 람다를 공부할 예정이다.
3. 정적 중첩 클래스(Static Nested Classes)
정적 중첩 클래스는 외부 클래스와 연관이 있다.
static class methods처럼 static nested class는 바로 외부 클래스에 정의된 메소드나 인스턴스 변수에 접근할 수 없다.
오로지 object reference로만 사용할 수 있다.
이게 무슨말이냐?
이해를 못하니 역시 예시를 주는 우리의 공식문서.... 예시 코드를 보자.
OuterClass.java
public class OuterClass {
String outerField = "Outer field";
static String staticOuterField = "Static outer field";
class InnerClass {
void accessMembers() {
System.out.println(outerField);
System.out.println(staticOuterField);
}
}
static class StaticNestedClass {
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field outerField
// System.out.println(outerField);
System.out.println(outer.outerField);
System.out.println(staticOuterField);
}
}
public static void main(String[] args) {
System.out.println("Inner class:");
System.out.println("------------");
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
innerObject.accessMembers();
System.out.println("\nStatic nested class:");
System.out.println("--------------------");
StaticNestedClass staticNestedObject = new StaticNestedClass();
staticNestedObject.accessMembers(outerObject);
System.out.println("\nTop-level class:");
System.out.println("--------------------");
TopLevelClass topLevelObject = new TopLevelClass();
topLevelObject.accessMembers(outerObject);
}
}
TopLevelClass.java
public class TopLevelClass {
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field OuterClass.outerField
// System.out.println(OuterClass.outerField);
System.out.println(outer.outerField);
System.out.println(OuterClass.staticOuterField);
}
}
해당 코드에서 outerField를 Static Nested Class에서 출력하려니 다음과 같이 컴파일 에러가 난다.
위에서 언급했듯이 정적 중첩 클래스는 외부 클래스의 인스턴스 맴버에 직접 접근할 수 없음을 확인했다.
근데 왜 그럴까?
이를 메모리 관점에서 보면
Static 으로 선언된 멤버들의 경우는 외부 객체가 없어도 독립적으로 메모리에 올라가게 된다.
결국 외부 객체는 아무런 연관이 없다는 것이다. 그런데 혼자 올라가는데 모르는 정보가 있으니 당연 에러가 나는 것이다.
여기서 메모리 관련해서는 나중에 다시 더 자세히 정리하려고 한다. 메모리... 기억이안나... lost my momory ㅠㅠ
4. Shadowing
어떤 타입의 선언(예: 멤버 변수나 매개변수 이름)이 특정 범위(예: 내부 클래스나 메서드 정의)에서 이루어졌고,
그 이름이 외부 범위의 다른 선언과 같다면, 그 선언은 외부 범위의 선언을 가린다(shadow).
가려진 선언은 이름만으로는 참조할 수 없다.
이게 또 무슨 말이야?
코드를 보자.
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
x가 ShadowTest class에 선언되어 있고, inner class인 FirstLevel에 선언되어 있다.
여기에 더해 inner class인 FirstLevel class 내부에 method의 매개변수로 x 가 선언되어 있다.
그러면 main method의 결과는???
this는 아무것도 붙이지 않고 사용하면 this를 사용하는 위치의 바로 상위의 class를 가르킨다.
그래서 결과는
x = 23
this.x = 1
ShadowTest.this.x = 0
이렇게 된다.
23을 매개변수로 넘겨줬으니 그대로 출력을 하고, FirstLevel에 x = 1 로 선언되어 있으니 this.x로 맞다.
그리고 enclosing class에 접근하기 위해서는 ShadowTest에 class의 x에 접근하는 방식으로 코딩을 했다.
즉, 선언이 가려져서 단순히 이름만으로 각각 원하는 곳에 접근하기 어려운 것이다.
이번에는 공식문서를 따라가며 읽다보니 여기까지 오게 되었다.
글도 사실 내가 새롭게 적은건 없고 그냥 읽어보고 이해 안되는것을 적었다.
원래 목적은 로컬 클래스, 익명클래스와 람다에 대해 공식문서를 읽는 거였는데...
자꾸 Inner class 어쩌구 저쩌구 거슬려서 어쩔 수 없었다.. ㅠㅠ 그렇다고 class부터 다시 다 읽기는 너무 싫어서 이정도 까지만 하려고 한다.
다음시간에 정말 이어서 읽을 예정이다! ㅠㅠ
출처:
https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
'programming language > Java' 카테고리의 다른 글
[Java] 익명 클래스(Anonymous Class) (2) | 2025.04.23 |
---|---|
[Java] : Local Class알아보기 (5) | 2025.04.19 |
[Java] ForkJoinPool 알아보기 (0) | 2024.01.28 |
[Java] 스레드 - Future, FutureTask, ComplatebleFuture? (2) | 2024.01.28 |
[Java] @annotation을 통한 Response 데이터 변경하기 (feat : jackson) (0) | 2023.12.01 |