티스토리 뷰
포스팅에 앞서 내용이 틀릴 수 있습니다.
해당 부분 지적 감사히 받습니다.
중첩 클래스와 내부 클래스에 대해 알아보자.
중첩 클래스와 내부 클래스는, 클래스 내부에 구현되어 있는 또 다른 클래스이다.
즉 메서드와 같이 외부 클래스의 하나의 요소가 되는 것이다.
다만 정적 중첩 클래스는 예외다.
정적 중첩 클래스는 static이 붙으며 이는 컴파일 시 메서드 영역에 생성된다.
따라서 구조상 외부 클래스의 내부에 선언되어 있지만, 둘 사이의 관계는 없다.
다만 정적 중첩 클래스는 외부 클래스와 위치가 같기에, 외부 클래스의 private 요소에 접근할 수 있는 차이점이 하나 존재한다.
하지만 static의 성질이기에, 정적 중첩 클래스 내부에 외부의 클래스 객체를 생성하여야 해당 private인스턴스로 접근이 가능하다.
내부 클래스는 외부 클래스의 하나의 요소와 같다.
내부 클래스는 non-static이다.
물론 정적 중첩 클래스에서 static이 빠지면 얘도 non-static이 된다.
따라서 외부 클래스의 인스턴스를 생성한 후, 접근이 가능하다.
마찬가지로 외부 클래스가 생성될 때, 내부 클래스는 외부 클래스의 참조값을 자동으로 갖게 된다.
( 외부 클래스 요소 접근 가능)
다만 내부 클래스의 인스턴스가 생성되는 것은 아니다.
내부 클래스에 접근하려면 추가적으로 내부 클래스의 인스턴스도 외부 클래스의 참조값을 통해 생성하여야 한다.
그렇다면 이렇게 중첩 클래스와 내부 클래스가 존재하게 된 이유가 무엇일까?
개발자 관점에서 소스를 볼 때, A클래스에서만 B를 사용하는데, B를 A와 같은 패키지 소속으로 만들어 놓으면 단순히 보기에 다른 곳에서도 쓰일 수 있다고 판단할 수도 있다.
이는 코드해석(유지보수 관점)에서 지장을 준다.
따라서 A클래스에서만 사용되는 B클래스를 아예 A클래스 내부로 집어넣어 코드해석을 원활하게 할 수 있도록 도와준다.
이는 중첩 클래스, 내부 클래스의 첫 번째 장점이다.
두 번째는 캡슐화이다.
내부 클래스가 외부 클래스의 요소에 접근할 수 있으니, 외부 클래스의 public 접근제한자를 private으로 더 강한 제어를 하여 외부에 불필요한 노출을 줄일 수 있다.
이를 정리해 보자
장점
- 논리적 그룹화 : 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩클래스를 외부로 노출시키지 않는다.
- 캡슐화 : 중첩은 바깥의 private에 접근할 수 있어 public접근제한자를 줄일 수 있다.
아래 코드를 보자.
import java.lang.reflect.Field;
public class LocalOuterV3 {
private int outInstanceVar = 3;
public Printer process(int paramVar) {
int localVar = 1; //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
class LocalPrinter implements Printer {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
//인스턴스는 지역 변수보다 더 오래 살아남는다.
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
//printer.print();를 여기서 실행하지 않고 Printer 인스턴스만 반환한다.
return printer;
}
public static void main(String[] args) {
LocalOuterV3 localOuter = new LocalOuterV3();
Printer printer = localOuter.process(2);
//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
printer.print();
//추가
System.out.println("필드 확인");
Field[] fields = printer.getClass().getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
}
}
LocalOuterV3라는 클래스 내부에 Printer 타입을 반환하는 process라는 메서드가 있고, 이 메서드 내부에는 LocalPrinter라는 클래스가 Printer를 구현하고 있다.
그리고 구현된 LocalPrinter 클래스를 반환하여 메인 함수에서 해당 참조값을 반환받아 print() 메서드를 사용하는 코드이며, 메인에서는 LocalPrinter의 지역변수 int value= 0, process 메서드의 localVar = 1, process메서드의 매개변수 paramVar = 2, LocalOuterV3의 클래스 변수 3을 출력하게 되어있다.
실행 결과가 어떻게 될까?
이렇게 모든 변수의 값을 잘 출력하는 것을 볼 수 있다.
하지만 자바의 메모리를 잘 이해한 사람이라면 이곳에서 무언가 이상함을 느껴야 한다.
바로 지역변수(value)의 생존 주기이다.
지역변수의 생존 주기는 해당 변수가 선언된 지역이 스택 프레임에서 처리될 때만 살아있는데, 분명 우리의 코드는 process() 메서드가 종료된 후에 print()를 호출하여 이미 종료된 process() 메서드의 지역 변수를 찾아왔다.
이게 어떻게 된 일일까?
바로 자바에서 제공하는 지역 변수 캡처 기능이다.
자바는 지역 클래스의 인스턴스를 생성할 때, "필요한" 지역변수를 생성하여 생성된 인스턴스(process)에 함께 넣어준다.
코드 외부적으로 보이진 않지만 자바단에서 처리해 주는 부분이다.
따라서 자바는 이 지역 변수가 스택 프레임에서 작업이 끝난 후에도 호출될 가능성이 있으면, 지역 클래스의 멤버변수로 할당해 준다.
즉 process메서드가 내부 클래스 LocalPrinter의 인스턴스가 생성될 때, 내부의 메서드 print()에서 외부 process() 지역 변수인 paramVar와 localVar를 불러오는 걸 확인 후, 이 값들을 LocalPrinter의 멤버 변수로 넣어준다는 것이다.
그렇기에 process() 메서드의 종료 시점 이후에도 내부 print()에 접근하였을 때, 해당 변수들을 모두 불러올 수 있었던 것이다.
그런데 여기에서도 깨림칙함이 느껴져야 한다.
근데 만약.. 지역 변수 캡처를 한 이후에 지역변숫값에 수정이 생기면 어떻게 되는 거지?
문제가 많다
결론적으로는 자바가 그렇게 못하게 막아놨다.
따라서 지역 변수는 final로 선언되거나, 사실상(effectively) final이어야 한다.
(사실상 final은 final로 선언은 되지 않았지만, 내부 로직상 해당 변숫값을 바꾸지 않는 것)
만약 지역 변수 값에 변동이 생기면, 동기화 문제가 발생하게 된다.
1. 지역변수 값이 변하면, 캡처된 값도 변해야 한다.
2. 인스턴스에 있는 캡처값이 변하면, 지역변수 또한 값이 변해야 한다.
3. 개발자관점에선 예상치 못한 곳에서 값이 변경될 수 있다. (사이드 이펙트 발생)
4. 멀티스레드 상황에서 이런 동기화에 큰 문제가 될 수 있다. ( 해결해도 성능저하)
이러한 문제들로 자바는 그냥 지역변수의 변동을 막아뒀다.
다음 시간엔 익명 클래스( + 람다)에 대해 알아보자.
'기술스택 > 자바(Spring)' 카테고리의 다른 글
자바 예외처리(Exception) (2) | 2025.02.20 |
---|---|
자바 익명 클래스 + 람다식(간단) (1) | 2025.02.18 |
자바 타입 안전 열거형 + Enum (1) | 2025.02.16 |
자바 래퍼 클래스 (Wrapper Class) (1) | 2025.02.15 |
자바 String Class (2부) (1) | 2025.02.13 |
- Total
- Today
- Yesterday
- 김영한
- extends
- 백준 피보나치
- webhacking.kr
- 프로그래머스
- 상속
- 코딩테스트
- static
- 상품을 구매한 회원 비율 구하기 파이썬
- 자바
- 스프링
- samron
- 프로그래머스 상품을 구매한 회원 비율 구하기 파이썬
- 김영한 실전 자바 중급
- lord of sql
- 김영한 실전 자바 기초
- zixem
- 코딩테스트 준비
- java
- 프로그래머스 상품을 구매한 회원 비율 구하기
- samron3
- ys.k
- 기술스택
- Los
- los 15단계
- 백준 피보나치 수열
- spring
- los 15
- 백준
- 김영한 실전 자바 기본
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |