jette x étend la signature de la méthode d’exception

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.
  • Est-ce que cela résoudra la ressortingction d’exception générique?
  • Pourquoi ne pas simplement throws Throwable , car le rest sera effacé par un effacement de type?

Et une question pas directement liée:

  • Cette méthode 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 exceptionSupplier) throws X 
  • X a une limite supérieure de Throwable . Ce sera important plus tard.
  • Nous retournons un type T qui est lié au T Optional
  • Nous attendons un Supplier qui a une limite supérieure générique de X
  • Nous lançons 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 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:

Capture d'écran de Javadoc

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 

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 de catch(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.