2009-02-01 7 views
116

Nach den Java Language Sepecification, 3. Auflage:Warum lässt Java generische Unterklassen von Throwable nicht zu?

It is a compile-time error if a generic class is a direct or indirect subclass of Throwable.

ich verstehen will, warum diese Entscheidung getroffen wurde. Was ist mit generischen Ausnahmen falsch?

(Soweit ich weiß, Generika sind einfach Kompilierung-syntaktischer Zucker, und sie werden zu Object ohnehin in den .class Dateien übersetzt werden, so effektiv eine generische Klasse zu erklären ist, als ob alles in ihm war ein Object. Bitte korrigieren Sie mich, wenn ich falsch liege)

+1

generisches Typ Argument durch die obere Schranke ersetzt werden, die standardmäßig Objekt ist. Wenn Sie etwas wie die Liste haben, wird A in den Klassendateien verwendet. –

+0

Danke @Torsten. Ich habe vorher nicht an diesen Fall gedacht. –

+2

Es ist eine gute Interviewfrage, diese. – skaffman

Antwort

128

als Mark sagte, sind die Typen nicht reifiable, was ein Problem im folgenden Fall ist:

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

Sowohl SomeException<Integer> und SomeException<String> auf die gleiche Art gelöscht werden, gibt es keine Möglichkeit für die JVM zu unterscheiden die Ausnahme-Instanzen und daher keine Möglichkeit zu sagen, welcher catch-Block ausgeführt werden soll.

+1

aber was bedeutet "verifizierbar"? – aberrant80

+1

@ aberrant80: Einfach ausgedrückt bedeutet das, dass Sie zur Laufzeit zum Concret-Typ gelangen können. Was ist das nicht in Java? –

+34

So sollte die Regel nicht sein "generische Typen können keine Throwable-Klasse ableiten", sondern "Fangklauseln müssen immer rohe Typen verwenden". – Archie

13

Hier ist ein einfaches Beispiel dafür, wie die Ausnahme zu verwenden.

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

der Körper der try-Anweisung wirft die Ausnahme mit einem bestimmten Wert, der durch gefangen die Fangklausel.

Im Gegensatz dazu ist die folgende Definition einer neuen Ausnahme verboten, weil es eine parametrisierte Art erstellt:

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

Ein Versuch, die oben genannten Berichte einen Fehler zu kompilieren:

% 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 

Diese Einschränkung ist sinnvoll, weil fast jeder Versuch, eine solche Ausnahme zu fangen, fehlschlagen muss, weil der Typ nicht verifizierbar ist. Man könnte eine typische Anwendung der Ausnahme erwartet in etwa wie folgt zu sein:

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; 
    } 
    } 
} 

Dies ist nicht zulässig, weil der Typ in der catch-Klausel nicht reifiable. Zum Zeitpunkt des Schreibens dieses Artikels, berichtet die Sun-Compiler eine Kaskade von Syntaxfehlern in einem solchen Fall:

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

Da Ausnahmen nicht parametrisch sein kann, wird die Syntax beschränkt, so dass der Typ muss als Kennung geschrieben werden, ohne folgenden Parameter

+2

Was meinst du, wenn du 'verifizierbar' sagst? "verifizierbar" ist kein Wort. – ForYourOwnGood

+1

Ich kannte das Wort selbst nicht, aber eine schnelle Suche in Google brachte mir das: http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.7 –

+0

https: // docs.oracle.com/javase/tutorial/java/generics/nonReifableVarargsType.html –

2

Ich würde erwarten, dass es ist, weil es keine Möglichkeit gibt, die Parametrisierung zu garantieren. Betrachten Sie den folgenden Code:

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

Wie Sie bemerken, Parametrisierung ist nur syntaktischer Zucker. Der Compiler versucht jedoch sicherzustellen, dass die Parametrisierung für alle Verweise auf ein Objekt im Kompilierungsbereich konsistent bleibt. Im Falle einer Ausnahme kann der Compiler nicht garantieren, dass MyException nur von einem Bereich ausgelöst wird, den es verarbeitet.

+0

Ja, aber warum wird es dann nicht als "unsicher" markiert, wie zum Beispiel bei Casts? – eljenso

+0

Weil Sie bei einer Umwandlung den Compiler "Ich weiß, dass dieser Ausführungspfad das erwartete Ergebnis liefert" sagen. Mit einer Ausnahme kann man nicht (für alle möglichen Ausnahmen) sagen: "Ich weiß, wo diese geworfen wurde." Aber wie ich oben sage, ist es eine Vermutung; Ich war nicht da. – kdgregory

+0

"Ich weiß, dass dieser Ausführungspfad das erwartete Ergebnis erzeugt." Du weißt es nicht, du hoffst es. Deshalb sind generische und Downcasts statisch unsicher, aber sie sind trotzdem erlaubt. Ich habe Torstens Antwort aufgewertet, weil ich das Problem sehe. Hier nicht. – eljenso

10

Es ist im Wesentlichen, weil es auf eine schlechte Weise entworfen wurde.

Dieses Problem verhindert sauberes abstraktes Design, z.,

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

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

Die Tatsache, dass eine catch-Klausel für Generika scheitern würde nicht verdinglicht werden, ist keine Entschuldigung dafür. Der Compiler könnte einfach konkrete generische Typen verbieten, die Throwable oder Generics innerhalb von catch-Klauseln erweitern.

+0

+1. meine Antwort - http://stackoverflow.com/questions/30759692/throws-x-extends-exception-method-signature/30770099#30770099 – ZhongYu

+0

Genau das, was ich denke –

+1

Die einzige Möglichkeit, die sie besser entworfen haben könnte, war durch Rendering ~ 10 Jahre Kundencode inkompatibel. Das war eine tragfähige Geschäftsentscheidung. Das Design war korrekt ... ** angesichts des Kontexts **. –

3

Generics werden zur Kompilierzeit auf Typkorrektheit überprüft. Die Information des generischen Typs wird dann in einem Prozeß Typ Löschen entfernt. Beispielsweise wird List<Integer> in den nicht generischen Typ List konvertiert.

Wegen Typ Typ löschen, Typ Parameter können nicht zur Laufzeit bestimmt werden.

Nehmen wir an, Sie dürfen Throwable wie folgt verlängern:

public class GenericException<T> extends Throwable 

Nun wollen wir den folgenden Code betrachten:

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

Aufgrund Typ Löschung, wird die Laufzeit nicht wissen, welche Fang Block zum Ausführen.

Daher ist es ein Kompilierungsfehler, wenn eine generische Klasse eine direkte oder indirekte Unterklasse von Throwable ist.

Quelle:Problems with type erasure

+0

Danke. Dies ist die gleiche Antwort wie die von Torsten (https://stackoverflow.com/a/501309/41283). –

+0

Nein, ist es nicht. Torstens Antwort hat mir nicht geholfen, weil es nicht erklärt hat, um welche Art von Löschung/Verdinglichung es sich handelt. –

Verwandte Themen