Java가 Throwable의 일반 서브 클래스를 허용하지 않는 이유는 무엇입니까?


146

Java Language Sepecification , 3 판 에 따르면 :

일반 클래스가의 직접 또는 간접 하위 클래스 인 경우 컴파일 타임 오류입니다 Throwable.

이 결정이 내려진 이유를 이해하고 싶습니다. 일반적인 예외의 문제점은 무엇입니까?

(내가 아는 한, 제네릭은 단순히 컴파일 타임 구문 설탕이며 파일 Object에서 어쨌든 번역 .class되므로 제네릭 클래스를 효과적으로 선언하는 것은 마치 마치 마치 모든 것이 Object. .)


1
제네릭 형식 인수는 상한으로 대체되며 기본적으로 Object입니다. List <와 같은 것이 있다면? A>를 확장하면 클래스 파일에서 A가 사용됩니다.
Torsten Marek

@Torsten에게 감사합니다. 나는 그 사건을 전에 생각하지 않았다.
Hosam Aly

2
좋은 면접 질문입니다.
skaffman

@TorstenMarek : 하나를 호출 myList.get(i)하면 get여전히을 반환합니다 Object. 컴파일러 A는 런타임에 제약 조건 중 일부를 캡처하기 위해 캐스트를 삽입합니까 ? 그렇지 않은 경우 OP는 결국 Object런타임 에 s로 내려갑니다 . (클래스 파일에는에 대한 메타 데이터가 포함되어 A있지만 메타 데이터 AFAIK뿐입니다.)
Mihai Danila

답변:


155

마크가 말했듯이 유형을 수정할 수 없으므로 다음과 같은 경우에 문제가됩니다.

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}

모두 SomeException<Integer>SomeException<String>동일한 유형으로 지워집니다, JVM이 예외 인스턴스를 구별 할 방법과 어떤 얘기하는 것이 방법이 없습니다 catch실행되어야 블록.


3
그러나 "reifiable"은 무엇을 의미합니까?
aberrant80

61
따라서 규칙은 "일반 유형은 Throwable을 서브 클래스로 분류 할 수 없습니다"가 아니라 "캐치 절은 항상 원시 유형을 사용해야합니다"여야합니다.
Archie

3
그들은 같은 유형의 두 catch 블록을 함께 사용하는 것을 허용하지 않을 수 있습니다. 따라서 SomeExc <Integer> 만 사용하는 것이 합법적이므로 SomeExc <Integer>와 SomeExc <String> 만 함께 사용하는 것은 불법입니다. 문제가되지 않습니까?
Viliam Búr

3
아, 이제 알겠다 내 솔루션은 RuntimeExceptions에 문제를 일으킬 수 있으며 선언 할 필요는 없습니다. 따라서 SomeExc가 RuntimeException의 하위 클래스 인 경우 SomeExc <Integer>를 던지고 명시 적으로 잡을 수는 있지만 다른 함수가 SomeExc <String>을 자동으로 던지거나 SomeExc <Integer>에 대한 내 catch 블록이 실수로 캐치합니다.
Viliam Búr

4
@ SuperJedi224-아니요. 제네릭 이전 버전과 호환 되어야 한다는 제약이 주어지면
Stephen C

14

다음은 예외 사용 방법에 대한 간단한 예입니다.

class IntegerExceptionTest {
  public static void main(String[] args) {
    try {
      throw new IntegerException(42);
    } catch (IntegerException e) {
      assert e.getValue() == 42;
    }
  }
}

TRy 문의 본문은 주어진 값으로 예외를 처리하며 이는 catch 절에 의해 포착됩니다.

반대로, 새 예외에 대한 다음 정의는 매개 변수화 된 유형을 작성하므로 금지됩니다.

class ParametricException<T> extends Exception {  // compile-time error
  private final T value;
  public ParametricException(T value) { this.value = value; }
  public T getValue() { return value; }
}

위의 컴파일을 시도하면 오류가보고됩니다.

% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception {  // compile-time error
                                     ^
1 error

이 예외는 유형을 수정할 수 없기 때문에 이러한 예외를 포착하려는 거의 모든 시도가 실패해야하기 때문에 합리적입니다. 예외의 일반적인 사용은 다음과 같습니다.

class ParametricExceptionTest {
  public static void main(String[] args) {
    try {
      throw new ParametricException<Integer>(42);
    } catch (ParametricException<Integer> e) {  // compile-time error
      assert e.getValue()==42;
    }
  }
}

catch 절의 유형을 확인할 수 없으므로 허용되지 않습니다. 이 글을 쓰는 시점에서 Sun 컴파일러는 다음과 같은 경우에 일련의 구문 오류를보고합니다.

% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
    } catch (ParametricException<Integer> e) {
                                ^
ParametricExceptionTest.java:8: ')' expected
  }
  ^
ParametricExceptionTest.java:9: '}' expected
}
 ^
3 errors

예외는 매개 변수가 될 수 없으므로 구문이 제한되어 형식이 다음 매개 변수없이 식별자로 작성되어야합니다.


2
'reifiable'이라고 말할 때 무엇을 의미합니까? 'reifiable'은 단어가 아닙니다.
ForYourOwnGood

1
나 자신은 그 단어를 몰랐지만 구글에서 빠른 검색은 나에게 이것을 얻었다 : java.sun.com/docs/books/jls/third_edition/html/…
Hosam Aly


13

본질적으로 나쁜 방식으로 설계 되었기 때문입니다.

이 문제는 깨끗한 추상 디자인을 방지합니다.

public interface Repository<ID, E extends Entity<ID>> {

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}

제네릭에 대한 catch 절이 실패한다는 사실은 그에 대한 변명이 아닙니다. 컴파일러는 캐치 절 내에서 Throwable을 확장하거나 제네릭을 허용하지 않는 구체적인 제네릭 형식을 허용하지 않을 수 있습니다.



1
그들이 더 잘 설계 할 수있는 유일한 방법은 ~ 10 년 동안 고객 코드를 호환하지 않는 것입니다. 그것은 가능한 사업 결정이었습니다. 디자인은 정확했다 ... 컨텍스트 주어진 .
Stephen C

1
그렇다면이 예외를 어떻게 잡을 수 있습니까? 작동하는 유일한 방법은 raw 유형을 잡는 것 EntityNotFoundException입니다. 그러나 이것은 제네릭을 쓸모 없게 만듭니다.
Frans

4

제네릭은 컴파일 타임에 형식이 올바른지 검사합니다. 그런 다음 일반 유형 정보는 type erasure 프로세스에서 제거됩니다 . 예를 들어, List<Integer>제네릭이 아닌 형식으로 변환됩니다 List.

유형 삭제 때문에 런타임에 유형 매개 변수를 판별 할 수 없습니다.

다음 Throwable과 같이 확장 할 수 있다고 가정 해 봅시다 .

public class GenericException<T> extends Throwable

이제 다음 코드를 고려해 봅시다.

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

erasure 유형 으로 인해 런타임은 실행할 catch 블록을 알 수 없습니다.

따라서 일반 클래스가 Throwable의 직접 또는 간접 서브 클래스 인 경우 컴파일 타임 오류입니다.

출처 : 유형 삭제 문제


감사. 이것은 Torsten이 제공 한 것과 같은 대답 입니다.
Hosam Aly

아닙니다. Torsten의 대답은 어떤 유형의 지우기 / 수정이 무엇인지 설명하지 않았기 때문에 나에게 도움이되지 않았습니다.
Good Night Nerd Pride

2

매개 변수화를 보장 할 수있는 방법이 없기 때문입니다. 다음 코드를 고려하십시오.

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

아시다시피, 매개 변수화는 단지 구문 설탕입니다. 그러나 컴파일러는 컴파일 범위의 개체에 대한 모든 참조에서 매개 변수화가 일관성을 유지하도록합니다. 예외의 경우 컴파일러는 MyException이 처리중인 범위에서만 발생 함을 보장 할 방법이 없습니다.


예,하지만 캐스트와 같이 왜 "안전하지 않은"것으로 표시되지 않습니까?
eljenso

캐스트를 사용하면 컴파일러에게 "이 실행 경로가 예상 결과를 생성한다는 것을 알고 있습니다." 예외적으로, 당신은 (가능한 모든 예외에 대해) "이것이 던져진 곳을 알고 있습니다"라고 말할 수 없습니다. 그러나 위에서 말했듯이 추측입니다. 나는 거기에 없었다.
kdgregory

"이 실행 경로가 예상 결과를 산출한다는 것을 알고 있습니다." 당신은 모른다, 당신은 그렇게 희망합니다. 그래서 일반 및 다운 캐스트는 정적으로 안전하지 않지만 그럼에도 불구하고 허용됩니다. 문제가 보이기 때문에 Torsten의 대답을 찬성했습니다. 여기 없어요
eljenso

객체가 특정 유형인지 알 수 없으면 캐스팅해서는 안됩니다. 캐스트의 전체 아이디어는 컴파일러보다 더 많은 지식을 가지고 있으며 해당 지식을 코드의 일부로 명시 적으로 만들고 있다는 것입니다.
kdgregory

예, MyException에서 MyException <Foo> 로의 확인되지 않은 변환을 수행하려고하므로 컴파일러보다 더 많은 지식이있을 수 있습니다. 어쩌면 당신은 "알아요"라는 것은 MyException <Foo> 일 것입니다.
eljenso
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.