En lisant le JavaDoc de Optional
, je suis tombé sur une signature de méthode étrange; Je n’ai jamais vu dans ma vie:
public T orElseThrow(Supplier exceptionSupplier) throws X extends Throwable
À première vue, je me suis demandé comment l’exception générique est même possible, puisque vous ne pouvez pas le faire ( ici et ici ). À bien y penser, cela commence à avoir un sens, car il ne s’agit ici que de lier le
Supplier
… mais le fournisseur lui-même sait exactement quel type il devrait être, avant les génériques.
Mais la deuxième ligne m’a frappé:
throws X
est un type d’exception générique complet. Et alors:
X extends Throwable
, qu’est- ce que cela signifie dans le monde?
X
est déjà lié à la signature de la méthode. throws Throwable
, car le rest sera effacé par un effacement de type? Et une question pas directement liée:
catch(Throwable t)
être capturée en tant que catch(Throwable t)
, ou par le type de Supplier
fourni? puisqu’il ne peut pas être vérifié au moment de l’exécution? Traitez-le comme n’importe quel autre code générique que vous avez lu.
Voici la signature formelle de ce que je vois dans le code source de Java 8 :
public T orElseThrow(Supplier extends X> exceptionSupplier) throws X
X
a une limite supérieure de Throwable
. Ce sera important plus tard. T
qui est lié au T
Optional
Supplier
qui a une limite supérieure générique de X
X
(qui est valide, puisque X
a une limite supérieure de Throwable
). Ceci est spécifié dans JLS 8.4.6 ; tant que X
est considéré comme un sous-type de Throwable
, sa déclaration est valide et légale ici. Il y a un bug ouvert sur le fait que le Javadoc est trompeur. Dans ce cas, il est préférable de faire confiance au code source plutôt qu’à la documentation jusqu’à ce que le bogue soit déclaré résolu.
En ce qui concerne la raison pour laquelle nous utilisons des throws X
au lieu des throws Throwable
: il est garanti que X
sera lié à Throwable
dans les limites les plus élevées. Si vous voulez un Throwable
plus spécifique (runtime, coché ou Error
), le simple fait de lancer Throwable
ne vous donnera pas cette flexibilité.
À votre dernière question:
Cette méthode devra-t-elle être capturée dans une clause catch (Throwable t)?
Quelque chose en bas de la chaîne doit gérer l’exception, que ce soit un bloc try...catch
ou la JVM elle-même. Idéalement, on voudrait créer un Supplier
lié à une exception qui correspond le mieux à ses besoins. Vous n’avez pas (et ne devriez probablement pas ) créer une catch(Throwable t)
pour ce cas; Si votre Supplier
est lié au type à une exception spécifique que vous devez gérer, il est préférable de l’utiliser comme sharepoint catch
plus tard dans la chaîne.
La signature de la méthode Javadoc est différente du code source. Selon la source b132 :
public T orElseThrow(Supplier extends X> exceptionSupplier) throws X { // Added by me: See? No X extends Throwable if (value != null) { return value; } else { throw exceptionSupplier.get(); } }
Confirmé: Ceci est un problème avec la génération Javadoc. En guise de test, j’ai créé un nouveau projet et généré Javadoc pour la classe suivante:
package com.stackoverflow.test; public class TestJavadoc { public static void doSomething() throws X { } }
Voici le Javadoc résultant:
Double confirmation: il y a un bogue ouvert sur la Javadoc pour cette classe
Java ne peut pas attraper les types d’exception génériques à cause de l’effacement
catch(FooException e) --analogy-> o instanceof List
catch
et instanceof
dépendent des informations de type à l’exécution; l’effacement ruine ça.
Cependant, il est un peu trop sévère d’interdire la déclaration générique de types d’exception tels que FooException
. Java pourrait le permettre; exige simplement que seuls les types bruts et les caractères génériques soient autorisés dans les catch
catch(FooException e) --analogy-> o instanceof List catch(FooException> e) o instanceof List>
Un argument peut être avancé que le type d’une exception est principalement intéressé par les clauses de capture; si nous ne pouvons pas attraper les types d’exceptions génériques, il est inutile de les avoir en premier lieu. D’ACCORD.
Maintenant, nous ne pouvons pas le faire non plus
catch(X e) // X is a type variable
alors pourquoi Java permet-il de lancer X
en premier lieu?
... foo() throws X
Comme nous le verrons plus tard, cette construction, aidée par l’effacement, pourrait en fait renverser le système de types.
Eh bien, cette fonctionnalité est très utile, du moins sur le plan conceptuel. Nous écrivons des méthodes génériques afin que nous puissions écrire du code de modèle pour les types d’entrée et de retour inconnus; Même logique pour les types de lancer! Par exemple
void logAndThrow(X ex) throws X ... throw ex; ... (IOException e){ logAndThrow(e); // throws IOException
Nous devons être capables de générer de manière générique pour obtenir une transparence des exceptions lors de l’abstraction d’un bloc de code. Un autre exemple, disons que nous sums fatigués d’écrire ce genre de passe-partout à plusieurs resockets
lock.lock(); try{ CODE }finally{ lock.unlock(); }
et nous voulons une méthode wrapper pour cela, en prenant un lambda pour le code
Util.withLock(lock, ()->{ CODE });
Le problème est que CODE peut lancer n’importe quel type d’exception. le passe-partout original jette tout ce que CODE jette; et nous voulons que l’expression writeLock()
fasse la même chose, à savoir la transparence des exceptions. Solution –
interface Code { void run() throws X; } void withLock(Lock lock, Code code) throws X ... code.run(); // throws X ... withLock(lock, ()->{ throws new FooException(); }); // throws FooException
Cela ne concerne que les exceptions vérifiées. Les exceptions non vérifiées peuvent se propager librement de toute façon.
L’un des défauts graves de l’approche des throws X
est qu’il ne fonctionne pas bien avec deux types d’exception ou plus.
D’accord, c’est vraiment sympa. Mais nous ne voyons aucune de ces utilisations dans les nouvelles API JDK. Aucun type fonctionnel d’aucun paramètre de méthode ne peut générer une exception vérifiée. Si vous faites Stream.map(x->{ BODY })
, BODY
ne peut pas lancer d’exceptions vérifiées. Ils détestent vraiment les exceptions vérifiées avec les lambdas.
Un autre exemple est CompletableFuture
, où l’exception est une partie centrale du concept entier; cependant, aucune exception vérifiée n’est autorisée dans les corps lambda.
Si vous êtes un rêveur, vous pourriez croire que dans une version future de Java, un mécanisme élégant de transparence des exceptions pour lambda sera inventé, rien de tel que ce laide throws X
hack; par conséquent, nous n’avons pas besoin de polluer nos API avec
pour l’instant. Ouais, rêve, mec.
Alors, combien de throws X
utilisés de toute façon?
Dans l’ensemble de la source JDK, la seule API publique qui le fait est Optional.orElseThrow()
(et ses cousins OptionalInt/Long/Double
). C’est tout.
(Il y a une lacune dans la conception orElseGet/orElseThrow
: la décision de créer une twig ne peut pas être prise dans le corps lambda. Nous pourrions plutôt concevoir une méthode plus générale.
T orElse( lambda ) throws X optional.orElse( ()->{ return x; } ); optional.orElse( ()->{ throw ex; } ); optional.orElse( ()->{ if(..)return x; else throw ex; } );
Outre orElseThrow
, la seule autre méthode de JDK à throws X
est la méthode non publique ForkJoinTask.uncheckedThrow()
. Il est utilisé pour “lancer sournois”, c’est-à-dire qu’un code peut renvoyer une exception vérifiée, sans que le compilateur et le runtime ne le sachent –
void foo() // no checked exception declared in throws { throw sneakyThrow( new IOExcepiton() ); }
ici, IOException
est lancé depuis foo()
à l’exécution; mais le compilateur et le runtime ne l’ont pas empêché. L’effacement est en grande partie à blâmer.
public static RuntimeException sneakyThrow(Throwable t) { throw Util.sneakyThrow0(t); } @SuppressWarnings("unchecked") private static T sneakyThrow0(Throwable t) throws T { throw (T)t; }
Il y a une différence entre les types génériques et les variables / parameters génériques.
Un type générique est un type qui déclare un paramètre générique. Par exemple
class Generic {}
Ce qui n’est pas autorisé, c’est un type générique (comme ci-dessus) qui étend également Throwable
.
class Generic extends Throwable {} // nono
Ceci est exprimé dans la spécification du langage Java, ici
C’est une erreur de compilation si une classe générique est une sous-classe directe ou indirecte de
Throwable
(§11.1.1).
Les variables génériques elles-mêmes, cependant, peuvent avoir n’importe quelle borne, générique ou autre, à condition que ce soit un type de référence.
Ce
X extends Throwable
signifie que tout type lié à X
au moment de la compilation (sans limite) doit être un sous-type de Throwable
.
X est déjà lié à la signature de la méthode.
X
n’est pas lié. La méthode déclare ses limites.
Cela résoudra-t-il à tout prix la ressortingction d’exception générique?
Non.
Pourquoi ne pas simplement
extends Throwable
, le rest sera effacé par type gommé?
Les génériques sont une fonctionnalité de compilation. L’effacement se produit après la compilation. Je crois qu’ils auraient pu simplement utiliser
Supplier extends Throwable>
comme type de paramètre. Mais alors vous perdriez la variable de type à utiliser dans la clause throws.
Posté:
Pourquoi ne pas simplement
throws Throwable
, car le rest sera effacé par un effacement de type?
Vous souhaitez que les throws
utilisent le même type d’ Exception
que celui fourni par le Supplier
.
Cette méthode
catch(Throwable t)
être prise dans une clause decatch(Throwable t)
?
Non / dépend Si vous liez une exception vérifiée à X
, vous devrez alors la gérer d’une manière ou d’une autre. Sinon, aucune catch
n’est “requirejse”. Mais quelque chose doit / devrait le gérer éventuellement, même si c’est la JVM elle-même.