ㅁ1. 지역 클래스
- 지역 클래스(Local class)는 내부 클래스의 특별한 종류의 하나이다. 따라서 내부 클래스의 특징을 그대로 가진다. 예를 들어 지역 클래스도 내부 클래스이므로 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.
- 지역 클래스는 지역 변수와 같이 코드 블럭 안에서 정의된다.
지역 클래스의 특징
- 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언한다
- 지역 클래스는 지역 변수에 접근할 수 있다.
package nested.nested.local;
public class LocalOuterV1 {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
class LocalPrinter {
int value = 0;
public void printDate() {
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.printDate();
}
public static void main(String[] args) {
LocalOuterV1 localOuterV1 = new LocalOuterV1();
localOuterV1.process(2);
}
}
실행결과
value= 0
localVar= 1
paramVar= 2
outInstanceVar= 3
지역 클래스의 접근 범위
- 자신의 인스턴스 변수인 value에 접근할 수 있다.
- 자신이 속한 코드 블럭의 지역 변수인 localVar에 접근할 수 있다.
- 자신이 속한 코드 블럭의 매개변수인 pramVar에 접근할 수 있다.
- 바깥 클래스의 인스턴스 멤버인 outInstanceVar에 접근할 수 있다.(지역 클래스도 내부 클래스의 한 종류이다)
지역 클래스는 지역 변수처럼 접근 제어자를 사용할 수 없다.
지역 변수 캡처1
변수의 생명 주기
- 클래스 변수: 프로그램 종료까지, 가장 길다(메서드 영역)
- 클래스 변수(static 변수)는 메서드 영역에 존재하고, 자바가 클래스 정보를 읽어 들이는 순간부터 프로그램 종료까지 존재한다.
- 인스턴스 변수: 인스턴스의 생존 기간(힙 영역)
- 인스턴스 변수는 본인이 소속된 인스턴스가 GC되기 전까지 존재한다. 생존 주기가 긴 편이다.
- 지역 변수: 메서드 호출이 끝나면 사라짐(스택 영역)
- 지역 변수는 스택 영역의 스택 프레임 안에 존재한다. 따라서 메서드가 호출 되면 생성되고, 메서드 호출이 종료되면 스택 프레임이 제거되면서 그 안에 있는 지역 변수도 모두 제거된다. 생존 주기가 아주 짧다. 참고로 매개변수도 지역 변수의 한 종류이다.
package nested.nested.local;
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 localOuterV1 = new LocalOuterV3();
Printer printer = localOuterV1.process(2);
//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
printer.print();
}
}
실행 결과
value= 0
localVar= 1
paramVar= 2
outInstanceVar= 3
process() 메서드가 종료되면서 내부의 지역 변수들이 제거되었는데 어떻게 LocalPrinter.print()로 접근이 가능한가?
지역 변수 캡처2
지역 클래스는 지역 변수에 접근할 수 있다.
그런데 앞서 본 것 처럼 지역 변수의 생명주기는 짧고, 지역 클래스를 통해 생성한 인스턴스의 생명 주기는 길다.
지역 클래스를 통해 생성한 인스턴스가 지역 변수에 접근해야 하는데, 둘의 생명주기가 다르기 때문에 인스턴스는 살아있지만, 지역 변수는 이미 제거된 상태일 수 있다.
지역 변수 캡쳐
자바는 이런 문제를 해결하기 위해 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 생성한 인스턴스에 함께 넣어둔다. 이런 과정을 변수 캡쳐(Capture)라 한다.
물론 모든 지역 변수를 캡쳐하는 것이 아니라 접근이 필요한 지역 변수만 캡쳐한다.
package nested.nested.local;
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 localOuterV1 = new LocalOuterV3();
Printer printer = localOuterV1.process(2);
//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
printer.print();
//추가
System.out.println("필드 확인");
Field[] fields = printer.getClass().getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
}
}
실행 결과
필드 확인
//인스턴스 변수
field = int nested.nested.local.LocalOuterV3$1LocalPrinter.value
//캡쳐 변수
field = final int nested.nested.local.LocalOuterV3$1LocalPrinter.val$localVar
field = final int nested.nested.local.LocalOuterV3$1LocalPrinter.val$paramVar
//바깥 클래스 참조
field = final nested.nested.local.LocalOuterV3 nested.nested.local.LocalOuterV3$1LocalPrinter.this$0
정리
지역 클래스는 인스턴스를 생성할 때 필요한 지역 변수를 먼저 캡처해서 인스턴스에 보관한다. 그리고 지역 클래스의 인스턴스를 통해 지역 변수에 접근하면, 실제로는 지역 변수에 접근하는 것이 아니라 인스턴스에 있는 캡쳐 변수에 접근한다.
지역 변수 캡쳐3
지역 클래스가 접근하는 지역 변수는 절대로 중간에 값이 변하면 안된다.
따라서 final로 선언하거나 또는 사실상 final이어야 한다. 이것은 자바 문법이고 규칙이다.
용어 - 사실상 final
영어로 effetively final이라 한다. 사실상 final 지역 변수는 지역 변수에 final 키워드를 사용하지는 않았지만, 값을 변경하지 않는 지역 변수를 뜻한다. final 키워드를 넣지 ㅇ낳았을 뿐이지, 실제로는 final 키워드를 넣은 것처럼 중간에 값을 변경하지 않은 지역 변수이다. 따라서 사실상 final 지역 변수는 fianl 키워드를 넣어도 동일하게 작동해야 한다.
캡쳐 변수의 값을 변경하지 못하는 이유
- 지역 변수의 값을 변경하면 인스턴스에 캡쳐한 변수의 값도 변경해야한다.
- 반대로 인스턴스에 있는 캡쳐 변수의 값을 변경하면 지역 변수의 값을 변경해야 한다.
- 개발자 입장에서 보면 예상하지 못한 곳에서 값이 변경될 수 있다. 이는 디버깅을 어렵게 한다.
- 지역 변수의 값과 인스턴스에 있는 캡쳐 변수의 값을 서로 동기화 해야하는데, 멀티쓰레드 상황에서 이런 동기화는 매우 어렵고, 성능에 나쁜 영향을 줄 수 있다.
2. 익명 클래스
익명 클래스를 사용하면 클래스의 이름을 생략하고, 클래스의 선언과 생성을 바로 할 수 있다.
package nested.nested.anonymous;
import nested.nested.local.Printer;
public class AnonymousOuter {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
Printer printer = new 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);
}
};
printer.print();
System.out.println("printer.class= " + printer.getClass());
}
public static void main(String[] args) {
AnonymousOuter main = new AnonymousOuter();
main.process(2);
}
}
실행 결과
value= 0
localVar= 1
paramVar= 2
outInstanceVar= 3
printer.class= class nested.nested.anonymous.AnonymousOuter$1
Printer 를 상속(구현) 하면서 바로 생성하는 것이다.
익명 클래스 특징
- 익명 클래스는 이름 없는 지역 클래스를 선언하면서 동시에 생성한다.
- 익명 클래스는 부모 클래스를 상속 받거나, 또는 인터페이스를 구현해야 한다. 익명 클래스를 사용할 대는 상위 클래스나 인터페이스가 필요하다.
- 익명 클래스는 말 그대로 이름이 없다. 이름을 가지지 않으므로, 생성자를 가질 수 없다 (기본 생성자만 사용됨)
- 익명 클래스는 AnonymousOuter$1과 같이 자바 내부에서 바깥 클래스 이름 + $ + 숫자로 정의된다. 익명 클래스가 여러개면 $1, $2, $3으로 숫자가 증가하면서 구분된다.
익명 클래스의 장점
익명 클래스를 사용하면 클래스를 별도로 정의하지 않고도 인터페이스나 추상 클래스를 즉석에서 구현할 수 있어 코드가 더 간결해진다. 하지만, 복잡하거나 재사용이 필요한 경우에는 별도의 클래스를 정의하는 것이 좋다.
익명 클래스를 사용할 수 없을 때
익명 클래스를 단 한번만 인스턴스를 생성할 수 있다. 여러번 생성해야 할 때는 지역 클래스를 선언하고 사용하면 된다.
정리
- 익명 클래스는 이름이 없는 지역 클래스이다.
- 특정 부모 클래스(인터페이스)를 상속 받고 바로 생성하는 경우 사용한다.
- 지역 클래스가 일회성으로 사용되는 경우나 간단한 구현을 제공할 때 사용한다.
활용1
활용2
정리
문자열 같은 데이터를 메서드에 전달할 때는 String, int와 같은 각 데이터에 맞는 타입을 전달하면 된다.
코드 조각을 메서드에 전달할 때는 인스턴스를 전달하고 해당 인스턴스에 있는 메서드를 호출하면 된다.
활용3
람다(lamda)
자바 8 이전까지 메서드에 인수로 전달할 수 있는것은 크게 2가지였다.
int, double과 같은 기본형 타입
Process Member와 같은 참조형 타입(인스턴스)
결국 메서드에 인수로 전달할 수 있는 것은 간단한 데이터나 인스턴스의 참조였다.
지금처럼 코드 조각을 전달하기 위해 클래스를 정의하고 메서드를 만들고 또 인스턴스를 꼭 생성해서 전달해야 할까?
정리
- 정적 중첩 클래스: 바깥 클래스와 밀접한 관련이 있지만, 인스턴스 간에 데이터 공유가 필요 없을 때 사용한다.
- 내부 클래스: 바깥 클래스의 인스턴스와 연결되어 있고, 바깥 클래스의 인스턴스 상태에 의존하거나 강하게 연관된 작업을 수행할 때 사용한다.
- 지역 클래스:
- 내부 클래스의 특징을 가진다.
- 지역 변수에 접글할 수 있다. 접근하는 지역 변수는 final 이거나 사실상 final 이어야 한다.
- 주로 특정 메서드 내에서만 간단히 사용할 목적으로 사용한다.
- 익명 클래스:
- 지역 클래스인데, 이름이 없다.
- 상위 타입을 상속 또는 구현하면서 바로 생성된다.
- 주로 특정 상위 타입을 간단히 구현해서 일회성으로 사용할 때 유용하다.
출처 - 김영한의 실전 자바 중급 1편
'Backend > Java' 카테고리의 다른 글
예외 처리2 (0) | 2024.08.13 |
---|---|
예외 처리 1 (0) | 2024.08.13 |
중첩 클래스, 내부 클래스 (0) | 2024.08.11 |
날짜와 시간 (0) | 2024.08.10 |
열거형 - ENUM (0) | 2024.08.09 |