특징 | Runnable | Callable |
---|---|---|
반환값 | 없음 (void) | 값 반환 (V) |
예외 처리 | 검사 예외(Checked Exception) 명시 불가 | 검사 예외 명시 가능 (throws Exception) |
주요 메서드 | void run() | V call() |
사용 목적 | 단순히 작업 실행 | 작업 실행 후 결과 반환, 예외 처리 필요 시 |
Runnable
과 Callable
의 예외 처리 관점에서의 차이는 두 인터페이스의 메서드가 검사 예외(Checked Exception)를 어떻게 처리하는지와 관련이 있습니다.
1. 검사 예외와 비검사 예외
검사 예외 (Checked Exception)
- Java 컴파일러가 명시적으로 처리하도록 요구하는 예외.
- 예:
IOException
,SQLException
,InterruptedException
. - 메서드에서 던지려면
throws
키워드를 사용해야 하며, 호출하는 쪽에서 반드시 try-catch로 처리하거나 throws로 위임해야 합니다.
비검사 예외 (Unchecked Exception)
- Java 컴파일러가 처리 여부를 강제하지 않는 예외.
- 예:
NullPointerException
,ArrayIndexOutOfBoundsException
. RuntimeException
을 상속받는 예외들이 해당되며, 명시적인 처리가 없어도 던질 수 있습니다.
2. Runnable과 Callable의 예외 처리 차이
Runnable: 검사 예외를 명시적으로 던질 수 없음
Runnable
의 run()
메서드는 throws
절이 없기 때문에 검사 예외를 직접 던질 수 없습니다.
@FunctionalInterface
public interface Runnable {
void run(); // throws Exception 불가능
}
결과:
- 검사 예외를
run()
메서드에서 던지고 싶다면, 반드시 try-catch로 내부에서 처리해야 합니다. - 호출하는 쪽에서는
Runnable
내부에서 발생한 예외를 직접 알 수 없습니다.
예제: Runnable에서 예외 처리
Runnable task = () -> {
try {
throw new Exception("예외 발생!"); // 검사 예외
} catch (Exception e) {
System.out.println("Runnable에서 예외 처리: " + e.getMessage());
}
};
new Thread(task).start(); // 출력: Runnable에서 예외 처리: 예외 발생!
Callable: 검사 예외를 명시적으로 던질 수 있음
Callable
의 call()
메서드는 throws Exception
절이 포함되어 있어, 검사 예외를 명시적으로 던질 수 있습니다.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception; // throws Exception 가능
}
결과:
- 검사 예외를
call()
메서드에서 던질 수 있으며, 호출하는 쪽에서 try-catch로 처리하거나 예외를 위임할 수 있습니다. - 호출하는 쪽에서는 작업 도중 발생한 예외를 명확히 알 수 있습니다.
예제: Callable에서 예외 처리
import java.util.concurrent.Callable;
public class CallableExample {
public static void main(String[] args) {
Callable<Integer> task = () -> {
throw new Exception("예외 발생!"); // 검사 예외
};
try {
task.call(); // 예외가 호출한 곳으로 전달됨
} catch (Exception e) {
System.out.println("Callable에서 예외 처리: " + e.getMessage());
}
}
}
3. Runnable과 Callable의 예외 처리 흐름 비교
(1) Runnable의 예외 처리 흐름
- 예외는
run()
메서드 내부에서만 처리 가능합니다. - 호출하는 쪽에서는 내부에서 발생한 예외를 직접 알 수 없습니다.
Runnable task = () -> {
try {
// 검사 예외를 처리
throw new Exception("예외 발생!");
} catch (Exception e) {
System.out.println("Runnable에서 처리: " + e.getMessage());
}
};
new Thread(task).start(); // 호출한 곳에서는 예외를 알 수 없음
(2) Callable의 예외 처리 흐름
call()
메서드는 예외를 던질 수 있으며, 호출하는 쪽에서 예외를 직접 처리할 수 있습니다.- 예외는
Future.get()
호출 시 확인할 수도 있습니다.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExceptionExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
throw new Exception("예외 발생!");
};
Future<Integer> future = executor.submit(task);
try {
future.get(); // Callable의 예외는 여기에 전달됨
} catch (Exception e) {
System.out.println("Callable 예외 처리: " + e.getCause().getMessage());
}
executor.shutdown();
}
}
4. 예외 처리 관점에서의 선택 기준
특징 | Runnable | Callable |
---|---|---|
검사 예외 처리 | 내부에서 처리해야 하며, 외부로 전달 불가 | 검사 예외를 던지고 호출한 쪽에서 처리 가능 |
비검사 예외 처리 | 내부에서 처리하거나 그대로 던질 수 있음 | 내부에서 처리하거나 그대로 던질 수 있음 |
사용 시점 | 결과 반환이나 예외 처리가 필요 없는 단순 작업 | 반환값이 필요하거나, 예외 처리가 작업의 중요한 부분일 때 사용 |
5. 결론
Runnable
:- 검사 예외는 내부에서 처리해야 하며, 호출하는 쪽에서는 이를 알 수 없습니다.
- 단순히 실행만 필요한 작업에서 적합합니다.
Callable
:- 검사 예외를 명시적으로 던질 수 있으며, 호출하는 쪽에서 이를 처리하거나 위임할 수 있습니다.
- 반환값이 필요하거나, 예외 처리가 중요한 작업에 적합합니다.
요약:
Runnable
은 간단한 작업에 적합하며, Callable
은 예외 처리와 결과 반환이 필요한 경우 유용합니다.
호출하는 위치에서 예외를 알 수 있는지
이 두 부분을 명확히 이해하려면, Runnable
과 Callable
의 예외 처리 흐름과 호출하는 쪽에서의 동작 차이를 중심으로 설명하겠습니다.
1. Runnable의 예외 처리 흐름
Runnable
의 run()
메서드는 검사 예외(Checked Exception)를 명시적으로 던질 수 없습니다. 따라서, Runnable
내부에서 발생한 예외는 반드시 try-catch
로 처리해야 합니다. 호출하는 쪽에서는 Runnable
내부에서 예외가 발생했는지 직접 알 수 없습니다.
예제: Runnable에서 예외 처리
Runnable task = () -> {
try {
throw new Exception("Runnable에서 예외 발생!");
} catch (Exception e) {
System.out.println("Runnable 내부에서 처리: " + e.getMessage());
}
};
// Runnable을 스레드에서 실행
Thread thread = new Thread(task);
thread.start();
출력 결과:
Runnable 내부에서 처리: Runnable에서 예외 발생!
특징
- 내부 처리 필수:
Runnable
에서 발생한 예외는run()
내부에서 처리해야 합니다.- 예외가 발생했는지 호출하는 쪽에서는 알 수 없습니다.
- 호출하는 쪽의 한계:
- 호출한 코드(
thread.start()
실행 위치)에서는Runnable
내부의 예외를 감지하거나 처리할 수 없습니다.
- 호출한 코드(
2. Callable의 예외 처리 흐름
Callable
의 call()
메서드는 검사 예외(Checked Exception)를 명시적으로 던질 수 있습니다. ExecutorService.submit()
을 통해 실행한 후, 반환된 Future
객체를 사용하면, 결과값뿐만 아니라 발생한 예외를 호출하는 쪽에서 직접 확인할 수 있습니다.
예제: Callable에서 예외 처리
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExceptionExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
throw new Exception("Callable에서 예외 발생!");
};
Future<Integer> future = executor.submit(task); // Callable 실행
try {
future.get(); // 결과를 가져오는 시점에서 예외 발생 여부 확인
} catch (Exception e) {
System.out.println("Callable 예외 처리: " + e.getCause().getMessage());
}
executor.shutdown();
}
}
출력 결과:
Callable 예외 처리: Callable에서 예외 발생!
Future.get()에서 예외 확인
future.get()
메서드를 호출하면:Callable
작업이 반환한 결과를 가져옴.- 작업 도중 예외가 발생했다면,
ExecutionException
으로 래핑되어 예외가 던져짐. - 호출한 쪽에서 예외를 확인하고 처리 가능.
특징
- 명시적으로 던질 수 있음:
Callable
의call()
메서드에서 발생한 예외는 호출하는 쪽(future.get()
)으로 전달됩니다.
- 호출하는 쪽에서 알 수 있음:
Future.get()
을 호출하면, 작업 도중 발생한 예외를 감지하고 처리할 수 있습니다.
3. Runnable과 Callable의 예외 처리 비교
특징 | Runnable | Callable |
---|---|---|
검사 예외 던지기 | 내부에서만 처리 가능 (try-catch 필수) |
call() 메서드에서 명시적으로 던질 수 있음 |
호출하는 쪽에서 예외 확인 | 불가능 (start() 호출 위치에서는 알 수 없음) |
Future.get() 호출 시 예외를 확인하고 처리 가능 |
예외 처리 방식 | 내부에서만 처리 (run() 메서드 안에서) |
호출하는 쪽에서 처리 가능 (future.get() 사용) |
4. 핵심 차이 요약
Runnable
- 호출하는 쪽에서는 내부에서 예외가 발생했는지 알 수 없습니다.
- 예외를 처리하려면
run()
메서드 내부에서try-catch
를 사용해야 합니다. - 호출하는 쪽은 작업이 성공했는지 실패했는지 알 수 없고, 결과값도 받을 수 없습니다.
Callable
- 호출하는 쪽에서는
Future.get()
을 통해 예외 발생 여부를 확인할 수 있습니다. call()
메서드에서 던진 예외는ExecutionException
으로 래핑되어 전달됩니다.- 호출하는 쪽은 작업 결과와 예외 상태를 모두 확인할 수 있습니다.
5. 비유를 통한 이해
- Runnable: "내부에서 문제가 생기면 알아서 해결하세요. 호출한 사람은 그게 뭔지 신경 쓰지 않아요."
- 호출자는 예외가 발생했는지 알 방법이 없습니다.
- Callable: "문제가 생기면 나중에 보고서를 줄게요. 보고서를 읽고 어떻게 처리할지 결정하세요."
- 호출자는
Future.get()
을 통해 예외와 결과를 확인할 수 있습니다.
- 호출자는
6. 어떤 상황에서 사용해야 할까?
Runnable
:- 단순히 실행만 필요한 작업.
- 결과값이나 예외 처리가 필요 없는 경우.
- 예: 로그 기록, UI 업데이트, 단순 반복 작업.
Callable
:- 결과값을 반환해야 하거나, 예외를 호출하는 쪽에서 처리해야 하는 작업.
- 예: 파일 다운로드, 데이터베이스 쿼리, 계산 작업.
'programming > JAVA' 카테고리의 다른 글
람다에서 표현식과 구문의 차이 (0) | 2025.01.18 |
---|---|
추상메서드에 abstract 키워드가 없는 이유 (0) | 2025.01.18 |
java) comparable, comparator의 비교 (0) | 2024.12.04 |
checked/unchecked Exception / @Transactional rollback (0) | 2023.05.06 |
자바의정석 14장. Optional 정리 (0) | 2022.02.24 |
댓글