나는 이것이 오래된 질문이라는 것을 알고 있지만 확인 된 예외로 레슬링을 보내고 있으며 추가 할 것이 있습니다. 그것의 길이 나를 용서 해주세요!
확인 된 예외를 가진 나의 주요 소고기는 다형성을 망치는 것입니다. 다형성 인터페이스로 멋지게 연주하는 것은 불가능합니다.
좋은 자바 List
인터페이스를 가져 가라 . 우리는 ArrayList
and 같은 일반적인 인 메모리 구현을 가지고 있습니다 LinkedList
. 또한 AbstractList
새로운 유형의 목록을 쉽게 디자인 할 수 있는 골격 클래스 가 있습니다. 읽기 전용 목록의 경우 두 가지 방법 만 구현하면됩니다 : size()
및 get(int index)
.
이 예제 WidgetList
클래스 Widget
는 파일에서 유형이 아닌 고정 크기 객체를 읽습니다 .
class WidgetList extends AbstractList<Widget> {
private static final int SIZE_OF_WIDGET = 100;
private final RandomAccessFile file;
public WidgetList(RandomAccessFile file) {
this.file = file;
}
@Override
public int size() {
return (int)(file.length() / SIZE_OF_WIDGET);
}
@Override
public Widget get(int index) {
file.seek((long)index * SIZE_OF_WIDGET);
byte[] data = new byte[SIZE_OF_WIDGET];
file.read(data);
return new Widget(data);
}
}
익숙한 List
인터페이스를 사용하여 위젯을 노출하면 자체 를 알 필요없이 항목을 검색 list.get(123)
하거나 ( for (Widget w : list) ...
) 목록을 반복 할 수 있습니다 ( ) WidgetList
. 이 목록을 일반 목록을 사용하는 모든 표준 메소드에 전달하거나로 묶을 수 Collections.synchronizedList
있습니다. 이 코드를 사용하는 코드는 "위젯"이 그 자리에서 만들어 졌는지, 어레이에서 가져 왔는지, 또는 파일이나 데이터베이스에서 읽거나 네트워크를 통해 읽거나 미래의 서브 스페이스 릴레이에서 읽거나 알 필요가 없습니다. List
인터페이스가 올바르게 구현 되었으므로 여전히 올바르게 작동합니다 .
그렇지 않다는 것을 제외하고. 위의 클래스는 파일 액세스 메소드가 IOException
"캐치 또는 지정"해야하는 확인 된 예외를 발생 시킬 수 있으므로 컴파일되지 않습니다 . 당신은 그것으로 던져 지정할 수 없습니다 - 즉 계약 위반 때문에 컴파일러가 당신을 못하게 List
인터페이스를. 그리고 WidgetList
나중에 설명 할 것처럼 자체적으로 예외를 처리 할 수있는 유용한 방법은 없습니다 .
분명히 할 일은 확인되지 않은 예외로 catch 된 예외를 catch하고 다시 던지는 것입니다.
@Override
public int size() {
try {
return (int)(file.length() / SIZE_OF_WIDGET);
} catch (IOException e) {
throw new WidgetListException(e);
}
}
public static class WidgetListException extends RuntimeException {
public WidgetListException(Throwable cause) {
super(cause);
}
}
((편집 : Java 8 UncheckedIOException
은이 경우를 위해 클래스를 추가했습니다 IOException
.
따라서 확인 된 예외 는 이와 같은 경우에는 작동하지 않습니다 . 당신은 그들을 던질 수 없습니다. Map
데이터베이스에 의해 뒷받침되는 영리함을위한 Ditto 또는 java.util.Random
COM 포트를 통해 양자 엔트로피 소스 에 연결된 구현 . 다형성 인터페이스의 구현으로 새로운 것을 시도하자마자 확인 된 예외 개념이 실패합니다. 그러나 확인 된 예외는 너무 교활하여 여전히 안전하지 않습니다. 낮은 수준의 메소드를 잡아서 다시 던져서 코드를 어수선하게하고 스택 추적을 어수선하게해야하기 때문입니다.
유비쿼터스 Runnable
인터페이스가 확인 된 예외를 던지는 무언가를 호출하면 종종이 코너로 돌아갑니다. 그대로 예외를 던질 수 없으므로 할 수있는 모든 것은 코드를 잡아서 다시 던져서 코드를 어지럽히는 것 RuntimeException
입니다.
실제로 해킹에 의존하면 선언되지 않은 확인 된 예외를 던질 수 있습니다 . 런타임시 JVM은 확인 된 예외 규칙을 신경 쓰지 않으므로 컴파일러 만 바보로 만들어야합니다. 가장 쉬운 방법은 제네릭을 남용하는 것입니다. 이것은 내 메소드입니다 (Java 8 이전에 일반 메소드의 호출 구문에 필요하기 때문에 클래스 이름이 표시됨).
class Util {
/**
* Throws any {@link Throwable} without needing to declare it in the
* method's {@code throws} clause.
*
* <p>When calling, it is suggested to prepend this method by the
* {@code throw} keyword. This tells the compiler about the control flow,
* about reachable and unreachable code. (For example, you don't need to
* specify a method return value when throwing an exception.) To support
* this, this method has a return type of {@link RuntimeException},
* although it never returns anything.
*
* @param t the {@code Throwable} to throw
* @return nothing; this method never returns normally
* @throws Throwable that was provided to the method
* @throws NullPointerException if {@code t} is {@code null}
*/
public static RuntimeException sneakyThrow(Throwable t) {
return Util.<RuntimeException>sneakyThrow1(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> RuntimeException sneakyThrow1(
Throwable t) throws T {
throw (T)t;
}
}
만세! 이것을 사용하여 선언하지 않고 스택에 깊이를 검사 RuntimeException
하지 않고 스택 추적 을 래핑 하지 않고 스택 추적을 혼란스럽게 만들 수 있습니다! "WidgetList"예제를 다시 사용하여 :
@Override
public int size() {
try {
return (int)(file.length() / SIZE_OF_WIDGET);
} catch (IOException e) {
throw sneakyThrow(e);
}
}
불행히도, 점검 된 예외의 최종 모욕은 컴파일러 가 결함이 있다고 판단 할 수없는 경우 점검 된 예외 를 잡을 수 없도록 거부한다는 것 입니다. (체크되지 않은 예외에는이 규칙이 없습니다.) 교묘하게 던져진 예외를 포착하려면 다음을 수행해야합니다.
try {
...
} catch (Throwable t) { // catch everything
if (t instanceof IOException) {
// handle it
...
} else {
// didn't want to catch this one; let it go
throw t;
}
}
다소 어색하지만 플러스 측면에서는에 싸여있는 확인 된 예외를 추출하는 코드보다 약간 간단합니다 RuntimeException
.
유감스럽게도, 예외를 다시 던지는 것에 대해 Java 7에 추가 된 규칙 덕분에 throw t;
유형 t
이 확인 되었지만 여기에서 합법적 입니다.
확인 된 예외가 다형성을 충족하면 반대의 경우도 문제가됩니다. 메소드가 잠재적으로 확인 된 예외를 throw하는 것으로 지정되었지만 재정의 된 구현은 그렇지 않습니다. 예를 들어, 추상 클래스 OutputStream
의 write
메소드는 모두 지정 throws IOException
합니다. ByteArrayOutputStream
실제 I / O 소스 대신 메모리 내 배열에 쓰는 서브 클래스입니다. 재정의 된 write
메소드는 IOException
s를 유발할 수 없으므로 throws
절이 없으므로 catch-or-specify 요구 사항에 대한 걱정없이 호출 할 수 있습니다.
항상 그렇지는 않습니다. Widget
스트림에 저장하는 방법이 있다고 가정하십시오 .
public void writeTo(OutputStream out) throws IOException;
이 방법으로 평범한 것을 받아들이 OutputStream
는 것이 옳은 일이므로 파일, 데이터베이스, 네트워크 등 모든 종류의 출력에 다형성으로 사용할 수 있습니다. 그리고 메모리 내 배열. 그러나 메모리 내 배열을 사용하면 실제로 발생할 수없는 예외를 처리해야하는 가짜 요구 사항이 있습니다.
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
someWidget.writeTo(out);
} catch (IOException e) {
// can't happen (although we shouldn't ignore it if it does)
throw new RuntimeException(e);
}
평소와 같이 확인 된 예외가 발생합니다. 변수가 개방형 예외 요구 사항이 더 많은 기본 유형으로 선언 된 경우 응용 프로그램에서 해당 예외 가 발생하지 않는다는 것을 알고 있더라도 해당 예외에 대한 처리기를 추가 해야합니다.
그러나 확인 된 예외는 실제로 너무 성가 시므로 반대의 경우도 마찬가지입니다! 현재 에 대한 호출에서 IOException
발생하는 모든 것을 포착한다고 가정 하지만 변수의 선언 유형을 a로 변경 하려고하면 컴파일러는 throw 할 수 없다고 확인 된 예외를 잡으려고 시도하면 요금을 청구합니다.write
OutputStream
ByteArrayOutputStream
이 규칙은 일부 터무니없는 문제를 일으 킵니다. 예를 들어, 세 가지 중 하나 write
의 방법 OutputStream
입니다 하지 에 의해 오버라이드 (override) ByteArrayOutputStream
. 특히 오프셋 0과 배열 길이 write(byte[] data)
로 호출하여 전체 배열을 작성하는 편리한 방법입니다 write(byte[] data, int offset, int length)
. ByteArrayOutputStream
3 인수 메서드를 재정의하지만 1 인수 편의 메서드를 그대로 상속합니다. 상속 된 메소드는 옳은 일을하지만 원치 않는 throws
절을 포함합니다 . 그것은 아마도의 디자인에 대한 감독일지도 ByteArrayOutputStream
모르지만 예외를 잡는 코드와의 소스 호환성을 깨뜨릴 수 있기 때문에 그것을 고칠 수는 없습니다. 예외는 결코, 결코, 결코 던져지지 않을 것입니다!
그 규칙은 편집과 디버깅 중에도 성가시다. 예를 들어, 때로는 메소드 호출을 일시적으로 주석 처리하고 확인 된 예외가 발생했을 경우 컴파일러는 이제 로컬 try
및 catch
블록 의 존재에 대해 불평 합니다. 그래서 나도 그 주석해야하고, 내 코드를 편집 할 때 때문에 이제 IDE가 잘못된 수준으로 들여 쓰기합니다 {
및이 }
주석하고 있습니다. 가! 작은 불만이지만 확인 된 예외를 확인하는 유일한 방법은 문제를 일으키는 것 같습니다.
거의 다 했어요. 확인 된 예외에 대한 나의 마지막 좌절 은 대부분의 콜 사이트 에서 그들과 함께 할 수있는 유용한 것이 없다는 것입니다. 이상적으로 문제가 발생하면 사용자에게 문제를 알리거나 작업을 적절하게 종료하거나 다시 시도 할 수있는 유능한 응용 프로그램 별 처리기가 있습니다. 전체 목표를 아는 유일한 사람이기 때문에 스택을 높은 처리기만이 작업을 수행 할 수 있습니다.
대신 우리는 다음 관용구를 얻습니다.이 관용구는 컴파일러를 종료하는 방법으로 만연합니다.
try {
...
} catch (SomeStupidExceptionOmgWhoCares e) {
e.printStackTrace();
}
GUI 또는 자동 프로그램에서 인쇄 된 메시지가 표시되지 않습니다. 더 나쁜 것은 예외 후에 나머지 코드로 진행됩니다. 예외는 실제로 오류가 아닌가? 그런 다음 인쇄하지 마십시오. 그렇지 않으면 다른 예외가 순간적으로 날아가고 그 시간까지 원래 예외 객체가 사라집니다. 이 관용구는 BASIC On Error Resume Next
이나 PHP 보다 낫지 않습니다 error_reporting(0);
.
어떤 종류의 로거 클래스를 호출하는 것은 그리 좋지 않습니다.
try {
...
} catch (SomethingWeird e) {
logger.log(e);
}
그것은 e.printStackTrace();
불확실한 상태의 코드와 마찬가지로 게으 르며 여전히 쟁기질입니다. 또한 특정 로깅 시스템 또는 다른 처리기의 선택은 응용 프로그램에 따라 다르므로 코드 재사용이 손상됩니다.
하지만 기다려! 응용 프로그램 별 처리기를 찾는 가장 쉽고 보편적 인 방법이 있습니다. 호출 스택보다 높거나 Thread의 catch되지 않은 예외 처리기 로 설정되어 있습니다. 따라서 대부분의 장소 에서 예외를 스택 위로 던지기 만하면 됩니다. 예, throw e;
. 확인 된 예외는 방해가됩니다.
나는 언어가 디자인되었을 때 확인 된 예외가 좋은 생각처럼 들릴 것이라고 확신하지만 실제로는 그것들이 모두 귀찮고 아무런 이점이 없다는 것을 알았습니다.