익명 클래스와 동작 전달
재사용된 익명 객체의 가변 상태 누적 문제를 관찰하고 지역·익명 클래스 구현으로 공통 전후 흐름과 가변 동작을 분리합니다.
익명 클래스는 이름 없이 상위 타입을 구현하거나 상속하면서 즉시 인스턴스를 만듭니다.
한 번만 필요한 작은 전략을 호출 위치 가까이에 둘 수 있습니다.
하지만 익명이라는 사실이 무상태를 보장하지 않으며 같은 인스턴스를 재사용하면 내부 필드도 계속 누적됩니다.
명령 재사용과 상태 누적
호출자는 매 실행이 독립적으로 1을 출력할 것으로 기대했지만 같은 익명 객체가 count 필드를 보관합니다.
public final class AnonymousStateReuseBug {
public static void main(String[] args) {
Process once = new Process() {
private int count;
@Override
public void run() {
count++;
System.out.println("count=" + count);
}
};
execute(once);
execute(once);
}
private static void execute(Process process) {
process.run();
}
private interface Process { void run(); }
}count=1
count=2익명 객체도 일반 객체와 같은 수명과 상태를 가집니다.
실행마다 새 인스턴스가 필요하면 팩터리에서 만들거나 상태를 지역 변수로 옮깁니다.
예시
다음은 원래 누적 예제와 별도로 실행하는 비교입니다. create를 호출할 때만 새 익명 객체가 만들어집니다. execute를 호출하는 것만으로 새 객체가 생기지는 않습니다.
public final class AnonymousFreshProcess {
public static void main(String[] args) {
execute(create());
execute(create());
Process reused = create();
execute(reused);
execute(reused);
}
private static Process create() {
return new Process() {
private int count;
public void run() { System.out.println("count=" + ++count); }
};
}
private static void execute(Process process) { process.run(); }
private interface Process { void run(); }
}count=1
count=1
count=1
count=2앞의 두 호출은 각각 새 객체, 뒤의 두 호출은 같은 reused입니다. 그 뒤 execute(reused)를 한 번 더 실행하면 3이고, execute(create())는 새 객체라 1입니다. 이 차이는 익명 여부보다 객체를 만드는 위치에서 결정됩니다.
정말 누적 명령이라면 이름 있는 클래스와 명시적 상태 메서드가 더 이해하기 쉽습니다.
같은 익명 객체에서 이어지는 count
같은 익명 객체를 재사용하며 count가 0→1→2로 바뀌고 두 출력이 누적됩니다. 새 객체 생성 없이 같은 대상의 상태를 넘겨 보는 대표적 사례입니다.
AnonymousStateReuseBug는 익명 Process를 한 번 생성하고 execute에 같은 once 참조를 두 번 전달한다. 한 객체의 count가 0에서 1, 다시 2로 증가한다.
익명 객체 · 재사용된 상태
같은 익명 객체에서 이어지는 count
익명 Process는 한 번만 생성됩니다. 같은 객체를 두 번 호출하므로 count는 0에서 1, 다시 2로 이어집니다.
한 번 생성
- 같은 객체의 count
int필드의 초기값0- 실제 출력
- 출력 없음
첫 execute
- 같은 객체의 count
run()의count++:0 → 1- 실제 출력
count=1
둘째 execute
- 같은 객체의 count
- 이전 값을 이어서
count++:1 → 2 - 실제 출력
count=2
execute는 process.run()만 호출하며 객체를 새로 만들거나 필드를 초기화하지 않습니다. 독립 실행을 원한다면 생성 지점이나 상태 위치를 바꿔야 합니다. 원문은 그 수정안을 실행한 예제가 아닙니다.
이 예의 조건
- once 익명 객체는 한 번 생성하고 재사용한다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 1 | 같은 익명 객체의 0에서 1,2로 이어지는 상태 |
| 2 | 상자는 새 객체 세 개가 아니라 같은 once 객체의 시간별 상태입니다. execute는 같은 process.run만 호출하며 객체나 필드를 다시 초기화하지 않습니다. 생성 때 출력은 없고 두 호출의 count 출력은 1,2입니다. |
| 구분 | 내용 |
|---|---|
| 설명 | 상자는 새 객체 세 개가 아니라 같은 once 객체의 시간별 상태입니다. execute는 같은 process.run만 호출하며 객체나 필드를 다시 초기화하지 않습니다. 생성 때 출력은 없고 두 호출의 count 출력은 1,2입니다. |
| 전체 읽기 정보 | 같은 익명 객체에서 이어지는 count 같은 익명 객체에서 이어지는 count AnonymousStateReuseBug는 익명 Process를 한 번 생성하고 execute에 같은 once 참조를 두 번 전달한다. 한 객체의 count가 0에서 1, 다시 2로 증가한다. 익명 객체 · 재사용된 상태 같은 익명 객체에서 이어지는 count 익명 Process는 한 번만 생성됩니다. 같은 객체를 두 번 호출하므로 count는 0에서 1, 다시 2로 이어집니다. 한 번 생성 같은 객체의 count int 필드의 초깃값 0 실제 출력 출력 없음 첫 execute 같은 객체의 count run() 의 count++ : 0 → 1 실제 출력 count=1 둘째 execute 같은 객체의 count 이전 값을 이어서 count++ : 1 → 2 실제 출력 count=2 execute 는 process.run() 만 호출하며 객체를 새로 만들거나 필드를 초기화하지 않습니다. 독립 실행을 원한다면 생성 지점이나 상태 위치를 바꿔야 합니다. 원문은 그 수정안을 실행한 예제가 아닙니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 객체 생성 count 0으로 once를 만듭니다. |
|
|
| 첫 execute 같은 run이 count를 늘립니다. |
|
|
| 둘째 execute 같은 객체를 다시 호출합니다. |
|
|
같은 익명 객체에서 이어지는 count
같은 익명 객체를 재사용하며 count가 0→1→2로 바뀌고 두 출력이 누적됩니다. 새 객체 생성 없이 같은 대상의 상태를 넘겨 보는 대표적 사례입니다.
AnonymousStateReuseBug는 익명 Process를 한 번 생성하고 execute에 같은 once 참조를 두 번 전달한다. 한 객체의 count가 0에서 1, 다시 2로 증가한다.
익명 객체 · 재사용된 상태
같은 익명 객체에서 이어지는 count
익명 Process는 한 번만 생성됩니다. 같은 객체를 두 번 호출하므로 count는 0에서 1, 다시 2로 이어집니다.
한 번 생성
- 같은 객체의 count
int필드의 초기값0- 실제 출력
- 출력 없음
첫 execute
- 같은 객체의 count
run()의count++:0 → 1- 실제 출력
count=1
둘째 execute
- 같은 객체의 count
- 이전 값을 이어서
count++:1 → 2 - 실제 출력
count=2
execute는 process.run()만 호출하며 객체를 새로 만들거나 필드를 초기화하지 않습니다. 독립 실행을 원한다면 생성 지점이나 상태 위치를 바꿔야 합니다. 원문은 그 수정안을 실행한 예제가 아닙니다.
이 예의 조건
- once 익명 객체는 한 번 생성하고 재사용한다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 1 | 같은 익명 객체의 0에서 1,2로 이어지는 상태 |
| 2 | 상자는 새 객체 세 개가 아니라 같은 once 객체의 시간별 상태입니다. execute는 같은 process.run만 호출하며 객체나 필드를 다시 초기화하지 않습니다. 생성 때 출력은 없고 두 호출의 count 출력은 1,2입니다. |
| 구분 | 내용 |
|---|---|
| 설명 | 상자는 새 객체 세 개가 아니라 같은 once 객체의 시간별 상태입니다. execute는 같은 process.run만 호출하며 객체나 필드를 다시 초기화하지 않습니다. 생성 때 출력은 없고 두 호출의 count 출력은 1,2입니다. |
| 전체 읽기 정보 | 같은 익명 객체에서 이어지는 count 같은 익명 객체에서 이어지는 count AnonymousStateReuseBug는 익명 Process를 한 번 생성하고 execute에 같은 once 참조를 두 번 전달한다. 한 객체의 count가 0에서 1, 다시 2로 증가한다. 익명 객체 · 재사용된 상태 같은 익명 객체에서 이어지는 count 익명 Process는 한 번만 생성됩니다. 같은 객체를 두 번 호출하므로 count는 0에서 1, 다시 2로 이어집니다. 한 번 생성 같은 객체의 count int 필드의 초깃값 0 실제 출력 출력 없음 첫 execute 같은 객체의 count run() 의 count++ : 0 → 1 실제 출력 count=1 둘째 execute 같은 객체의 count 이전 값을 이어서 count++ : 1 → 2 실제 출력 count=2 execute 는 process.run() 만 호출하며 객체를 새로 만들거나 필드를 초기화하지 않습니다. 독립 실행을 원한다면 생성 지점이나 상태 위치를 바꿔야 합니다. 원문은 그 수정안을 실행한 예제가 아닙니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 객체 생성 count 0으로 once를 만듭니다. |
|
|
| 첫 execute 같은 run이 count를 늘립니다. |
|
|
| 둘째 execute 같은 객체를 다시 호출합니다. |
|
|
익명 클래스의 선언과 생성
지역 클래스는 이름을 선언하고 new로 생성하지만 익명 클래스는 new Interface() { ... } 안에서 구현과 생성을 함께 합니다.
생성자 이름은 없으므로 초기 값은 캡처하거나 인스턴스 초기화 블록·필드 초기화를 사용하지만 복잡한 초기화에는 이름 있는 클래스가 낫습니다.
public final class AnonymousPostPrinter {
public static void main(String[] args) {
String prefix = "daily";
Printer printer = new Printer() {
@Override
public void print(String title, int viewCount) {
System.out.println(prefix + ":" + title + "=" + viewCount);
}
};
printer.print("anonymous", 40);
System.out.println("type=" + printer.getClass().isAnonymousClass());
}
private interface Printer {
void print(String title, int viewCount);
}
}daily:anonymous=40
type=trueprefix는 사실상 final로 캡처됩니다.
익명 클래스의 이름을 써서 다른 위치에서 직접 생성할 수는 없습니다. 다만 만든 객체를 참조로 전달하거나 다시 호출하는 것은 가능합니다.
공통 흐름과 가변 동작의 분리
주사위, 합계, 게시글 출력마다 시작·종료 메시지를 복사하면 정책 변경이 여러 곳에 퍼집니다.
hello(Process) 같은 실행기가 공통 전후 흐름을 소유하고 Process 구현이 가운데 동작만 제공합니다.
import java.util.Random;
public final class ProcessTemplateRefactoring {
public static void main(String[] args) {
Process dice = new Process() {
private final Random random = new Random(7);
public void run() { System.out.println("dice=" + (random.nextInt(6) + 1)); }
};
Process sum = new Process() {
public void run() { System.out.println("sum=" + (1 + 2 + 3)); }
};
execute("dice", dice);
execute("sum", sum);
}
private static void execute(String name, Process process) {
System.out.println("start=" + name);
process.run();
System.out.println("end=" + name);
}
private interface Process { void run(); }
}start=dice
dice=5
end=dice
start=sum
sum=6
end=sum실행기는 구체 동작을 모르고 Process 계약만 호출합니다.
예외가 나도 종료 로그를 남겨야 한다면 try/finally 정책을 실행기에 둡니다.
익명 클래스 구현마다 전후 흐름을 복사하지 않습니다.
게시글 명령의 즉시 전달
변수에 담지 않고 메서드 인수 자리에서 익명 객체를 만들 수 있습니다.
구현이 짧고 재사용하지 않을 때 의도가 가장 가까이 있습니다.
public final class InlineAnonymousBoardCommand {
public static void main(String[] args) {
execute(new BoardCommand() {
@Override
public PostResult run() {
return new PostResult("nested", 50);
}
});
}
private static void execute(BoardCommand command) {
System.out.println("before");
PostResult result = command.run();
System.out.println("saved=" + result.title() + ":" + result.viewCount());
System.out.println("after");
}
private interface BoardCommand { PostResult run(); }
private record PostResult(String title, int viewCount) { }
}before
saved=nested:50
after메서드가 하나인 함수형 인터페이스라면 람다로 더 짧게 표현할 수 있습니다.
여러 메서드를 구현하거나 자체 필드, 초기화, 명시적 this가 필요하면 익명 클래스가 여전히 의미를 가집니다.
this와 객체 수명
익명 클래스 본문의 this는 그것을 만든 바깥 객체가 아니라 새 익명 객체를 가리킵니다.
바깥 인스턴스와 연결된 문맥에서는 Owner.this처럼 한정해 그 인스턴스를 가리킵니다. static 문맥에서 선언한 익명 클래스에는 해당 바깥 인스턴스 연결이 없습니다.
이 차이를 놓치면 로그에 소유자 이름을 넣으려다 익명 객체의 toString()을 출력하거나, 같은 이름의 필드를 잘못 읽는 버그가 생깁니다.
람다는 새로운 this를 도입하지 않고 주변 문맥의 this 의미를 유지하므로 두 표현을 단순히 문법만 다른 것으로 보면 안 됩니다. 이것은 객체 할당이 없다는 뜻은 아니며, 람다 평가는 새 객체를 만들거나 기존 객체를 참조할 수 있습니다.
예시
this의 차이는 다음 독립 파일에서도 확인할 수 있습니다. buildAndRun은 인스턴스 메서드입니다. 두 구현은 같은 Command 역할을 제공하지만 this의 수신자가 다릅니다. 람다 본문의 이름 해석 규칙은 주변 문맥의 this를 유지합니다.
public final class AnonymousAndLambdaThis {
private final String name = "owner";
public static void main(String[] args) {
new AnonymousAndLambdaThis().buildAndRun();
}
private void buildAndRun() {
Command anonymous = new Command() {
private final String name = "anonymous";
public String run() {
return "this=" + this.name
+ ", outer=" + AnonymousAndLambdaThis.this.name;
}
};
Command lambda = () -> "lambda=" + this.name;
System.out.println(anonymous.run());
System.out.println(lambda.run());
}
private interface Command { String run(); }
}this=anonymous, outer=owner
lambda=owner익명 구현의 this.name은 자체 필드, 한정한 바깥 this는 owner 필드입니다. 람다는 새 this를 도입하지 않아 owner를 읽습니다. 이 파일의 생성 위치가 static main 안으로 바뀐다면 사용할 주변 인스턴스 this가 없다는 조건부터 다시 확인해야 합니다.
캡처한 값의 수명도 함께 봐야 합니다.
반환된 익명 객체가 오래 보관되면 그 객체가 참조한 바깥 인스턴스와 필드도 회수되지 않을 수 있습니다.
화면이나 게시글처럼 큰 소유자를 장기 캐시에 붙인 명령이 특히 위험합니다.
필요한 작은 값만 지역 변수로 복사해 캡처하거나, 장기 보관할 전략은 정적 중첩·최상위 구현으로 올려 암묵적 소유자 연결을 끊습니다.
익명 클래스의 JVM 이름은 컴파일 순서에 따라 Owner$1처럼 정해지는 구현 세부 사항입니다.
그 이름을 설정 파일, 직렬화 식별자, 리플렉션 분기 조건으로 저장하면 코드 위치가 바뀌기만 해도 호환성이 깨집니다.
외부 경계에는 인터페이스 이름과 명시적인 데이터만 노출하고, 익명 객체는 프로세스 내부의 일회성 구현으로 제한합니다.
따라서 선택 기준은 줄 수만이 아닙니다.
호출 즉시 실행되고 상태가 없으며 외부로 새지 않는 짧은 동작이면 익명 클래스가 적합합니다.
객체가 누적 상태를 갖거나 수명이 호출보다 길고, 장애 로그에서 구현 이름을 식별해야 하거나 독립 테스트가 필요하면 이름 있는 구현으로 승격합니다.
같은 동작을 매번 새로 만들지, 한 객체를 의도적으로 재사용할지도 생성 지점에서 드러내야 합니다.
익명 클래스 인스턴스 메서드의 this는 익명 객체다. 바깥 인스턴스 연결 여부는 선언 문맥에 달려 있으며 람다는 새 this를 도입하지 않는다. 객체 할당 여부와 this 의미는 별개다.
this 계약 · 주변 문맥
익명 클래스의 this와 바깥 문맥
익명 인스턴스 메서드의 this는 익명 객체입니다. 람다는 주변 this 의미를 사용하며, static main에는 바깥 this가 없습니다.
| 표현·문맥 | this 또는 연결의 의미 | 원문에 적용할 경계 |
|---|---|---|
| 익명 인스턴스 메서드 | this는 그 메서드가 호출된 익명 객체를 가리킵니다. | 자체 필드를 가질 수 있으며 같은 객체를 재사용하면 그 상태가 이어집니다. |
바깥 Owner.this | 해당 바깥 인스턴스와 연결된 문맥에서만 그 인스턴스를 가리킬 수 있습니다. | 원문의 static main 안에서 선언된 익명 클래스에는 해당 바깥 인스턴스 연결이 없습니다. 지역 값 사용은 가능합니다. |
| 람다 본문 | 새 this를 도입하지 않고 주변 문맥의 의미를 사용합니다. | 주변이 static 문맥이면 사용할 바깥 this도 없습니다. |
- 익명 인스턴스 메서드
- this 또는 연결의 의미
this는 그 메서드가 호출된 익명 객체를 가리킵니다.- 원문에 적용할 경계
- 자체 필드를 가질 수 있으며 같은 객체를 재사용하면 그 상태가 이어집니다.
- 바깥
Owner.this - this 또는 연결의 의미
- 해당 바깥 인스턴스와 연결된 문맥에서만 그 인스턴스를 가리킬 수 있습니다.
- 원문에 적용할 경계
- 원문의
static main안에서 선언된 익명 클래스에는 해당 바깥 인스턴스 연결이 없습니다. 지역 값 사용은 가능합니다.
- 람다 본문
- this 또는 연결의 의미
- 새
this를 도입하지 않고 주변 문맥의 의미를 사용합니다. - 원문에 적용할 경계
- 주변이
static문맥이면 사용할 바깥this도 없습니다.
AnonymousPostPrinter는 static main의 지역 값 prefix를 사용해 daily:anonymous=40을 출력하고 익명 클래스 여부는 type=true입니다. 람다 평가는 새 객체를 만들 수도, 기존 객체를 참조할 수도 있으므로 “할당 없음”으로 일반화하지 않습니다.
지역·익명·람다의 선택
- 같은 메서드 안에서 구현을 두 번 이상 생성하고 이름으로 설명하고 싶다: 지역 클래스
- 한 번만 쓰고 여러 메서드나 필드가 필요하다: 익명 클래스
- 함수형 인터페이스의 한 동작만 전달한다: 람다
- 여러 위치에서 재사용하고 독립 테스트한다: member 또는 최상위 class
익명 구현이 수십 줄로 커지면 이름과 파일을 주어 책임을 드러냅니다.
호출부 근접성보다 읽기 흐름과 재사용 가능성이 중요해지는 지점입니다.
연습 문제
Process 실행 중 IllegalStateException이 발생해도 end가 출력되도록 execute를 작성하세요.
main에서 예외를 잡아 메시지를 확인합니다.
Process.run은 IllegalStateException("board failed")를 던집니다. execute는 진입 때 start, 종료 처리 때 end를 출력하고, main은 caught=예외메시지를 출력하는 것으로 정합니다. Process 자체는 예외를 던지기 전에 문장을 출력하지 않습니다.
독립 연습에서는 run이 예외 대신 body를 출력하고 정상 반환하도록 바꿉니다. 세 로그의 순서와 main의 catch가 실행되는지도 예상하세요.
해설 보기
public final class AnonymousFinallyExercise {
public static void main(String[] args) {
try {
execute(new Process() {
public void run() { throw new IllegalStateException("board failed"); }
});
} catch (IllegalStateException error) {
System.out.println("caught=" + error.getMessage());
}
}
private static void execute(Process process) {
System.out.println("start");
try {
process.run();
} finally {
System.out.println("end");
}
}
private interface Process { void run(); }
}start
end
caught=board failed이 예제의 finally는 end를 출력하고 정상 완료하므로 원래 예외가 main의 catch로 전달됩니다.
독립 정상 입력의 순서는 start → body → end이고 catch는 실행되지 않습니다. finally의 실행이 곧 예외 발생이나 예외 복구를 뜻하지는 않습니다. 이 흐름은 JLS의 try/finally 실행 규칙에서 finally가 정상 완료하는 경우에 해당합니다.
다음 장에서 예외를 어디서 복구하고 어디로 전달할지 더 깊게 다룹니다.
일반 종료 문장과 finally의 실제 실행 순서
정상 callback과 별도 예외/finally의 실제 출력 순서를 대비합니다. run의 던짐→finally 정상 완료→main catch 순서를 단계화하면 정리와 복구의 차이가 드러납니다.
ProcessTemplateRefactoring은 정상 run 뒤에 일반 end 문장을 실행한다. AnonymousFinallyExercise는 run이 던진 예외 뒤에 finally의 end를 실행하고 원래 예외를 main이 잡는다.
콜백 처리 · 정상과 예외 경로
일반 종료 문장과 finally의 실제 실행 순서
서로 다른 execute 구현을 비교합니다. 정상 예제의 후속 end와 예외 예제의 finally는 보장하는 경로가 다릅니다.
정상 원문 · dice 먼저
- 실행기 진입
start=dicerun()안dice=5- 종료 처리
end=dice일반 후속 문장입니다.
- 호출자로 돌아감
- 다음
sum을 실행합니다.start=sum sum=6 end=sum
예외 원문 · finally 사용
- 실행기 진입
startrun()안IllegalStateException메시지
"board failed"로 발생합니다. 이 동작 자체의 출력은 없습니다.- 종료 처리
endfinally에서 출력하고 정상 완료합니다.- 호출자로 돌아감
- 원래 예외가 main의 catch로 전달됩니다.
caught=board failed
정상 원문의 end는 run이 예외를 던져도 보장되는 finally가 아닙니다. 예외 원문에서는 finally가 정상 완료하므로 원래 예외가 유지됩니다. finally 자체가 비정상 완료하면 이 경로를 그대로 일반화할 수 없습니다.
이 예의 조건
- 정상 callback과 예외/finally는 별도 프로그램이다.
- finally가 정상 완료하는 원 경로이다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | 일반 종료 문장과 finally의 실제 실행 순서 일반 종료 문장과 finally의 실제 실행 순서 ProcessTemplateRefactoring은 정상 run 뒤에 일반 end 문장을 실행한다. AnonymousFinallyExercise는 run이 던진 예외 뒤에 finally의 end를 실행하고 원래 예외를 main이 잡는다. 콜백 처리 · 정상과 예외 경로 일반 종료 문장과 finally의 실제 실행 순서 서로 다른 execute 구현을 비교합니다. 정상 예제의 후속 end와 예외 예제의 finally는 보장하는 경로가 다릅니다. 정상 원문 · dice 먼저 실행기 진입 start=dice run() 안 dice=5 종료 처리 end=dice 일반 후속 문장입니다. 호출자로 돌아감 다음 sum 을 실행합니다. start=sum sum=6 end=sum 예외 원문 · finally 사용 실행기 진입 start run() 안 IllegalStateException 메시지 "board failed" 로 발생합니다. 이 동작 자체의 출력은 없습니다. 종료 처리 end finally 에서 출력하고 정상 완료합니다. 호출자로 돌아감 원래 예외가 main의 catch로 전달됩니다. caught=board failed 정상 원문의 end는 run이 예외를 던져도 보장되는 finally가 아닙니다. 예외 원문에서는 finally가 정상 완료하므로 원래 예외가 유지됩니다. finally 자체가 비정상 완료하면 이 경로를 그대로 일반화할 수 없습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| execute 진입 현재 callback 실행 전에 시작을 출력합니다. |
|
|
| callback 실행 정상 dice는 5를 출력하고 별도 run은 예외를 던집니다. |
|
|
| 마무리와 호출자 정상은 end 뒤 sum, 예외는 finally end 뒤 catch로 갑니다. |
|
|
일반 종료 문장과 finally의 실제 실행 순서
정상 callback과 별도 예외/finally의 실제 출력 순서를 대비합니다. run의 던짐→finally 정상 완료→main catch 순서를 단계화하면 정리와 복구의 차이가 드러납니다.
ProcessTemplateRefactoring은 정상 run 뒤에 일반 end 문장을 실행한다. AnonymousFinallyExercise는 run이 던진 예외 뒤에 finally의 end를 실행하고 원래 예외를 main이 잡는다.
콜백 처리 · 정상과 예외 경로
일반 종료 문장과 finally의 실제 실행 순서
서로 다른 execute 구현을 비교합니다. 정상 예제의 후속 end와 예외 예제의 finally는 보장하는 경로가 다릅니다.
정상 원문 · dice 먼저
- 실행기 진입
start=dicerun()안dice=5- 종료 처리
end=dice일반 후속 문장입니다.
- 호출자로 돌아감
- 다음
sum을 실행합니다.start=sum sum=6 end=sum
예외 원문 · finally 사용
- 실행기 진입
startrun()안IllegalStateException메시지
"board failed"로 발생합니다. 이 동작 자체의 출력은 없습니다.- 종료 처리
endfinally에서 출력하고 정상 완료합니다.- 호출자로 돌아감
- 원래 예외가 main의 catch로 전달됩니다.
caught=board failed
정상 원문의 end는 run이 예외를 던져도 보장되는 finally가 아닙니다. 예외 원문에서는 finally가 정상 완료하므로 원래 예외가 유지됩니다. finally 자체가 비정상 완료하면 이 경로를 그대로 일반화할 수 없습니다.
이 예의 조건
- 정상 callback과 예외/finally는 별도 프로그램이다.
- finally가 정상 완료하는 원 경로이다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | 일반 종료 문장과 finally의 실제 실행 순서 일반 종료 문장과 finally의 실제 실행 순서 ProcessTemplateRefactoring은 정상 run 뒤에 일반 end 문장을 실행한다. AnonymousFinallyExercise는 run이 던진 예외 뒤에 finally의 end를 실행하고 원래 예외를 main이 잡는다. 콜백 처리 · 정상과 예외 경로 일반 종료 문장과 finally의 실제 실행 순서 서로 다른 execute 구현을 비교합니다. 정상 예제의 후속 end와 예외 예제의 finally는 보장하는 경로가 다릅니다. 정상 원문 · dice 먼저 실행기 진입 start=dice run() 안 dice=5 종료 처리 end=dice 일반 후속 문장입니다. 호출자로 돌아감 다음 sum 을 실행합니다. start=sum sum=6 end=sum 예외 원문 · finally 사용 실행기 진입 start run() 안 IllegalStateException 메시지 "board failed" 로 발생합니다. 이 동작 자체의 출력은 없습니다. 종료 처리 end finally 에서 출력하고 정상 완료합니다. 호출자로 돌아감 원래 예외가 main의 catch로 전달됩니다. caught=board failed 정상 원문의 end는 run이 예외를 던져도 보장되는 finally가 아닙니다. 예외 원문에서는 finally가 정상 완료하므로 원래 예외가 유지됩니다. finally 자체가 비정상 완료하면 이 경로를 그대로 일반화할 수 없습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| execute 진입 현재 callback 실행 전에 시작을 출력합니다. |
|
|
| callback 실행 정상 dice는 5를 출력하고 별도 run은 예외를 던집니다. |
|
|
| 마무리와 호출자 정상은 end 뒤 sum, 예외는 finally end 뒤 catch로 갑니다. |
|
|