J'avais une raison d'aller dans le code source de Throwable.getCause
aujourd'hui et Throwable.getCause
été un peu surpris de voir ce code dans cette méthode:
public synchronized Throwable getCause() { return (cause==this ? null : cause); }
Cela vient de Java 1.8, mais cela a la même apparence dans les versions ultérieures que j'ai examinées.
Ma question est la suivante: pourquoi ne pas simplement return cause
et en finir?
3 Réponses :
Il ne compare pas cause
à null
, il compare cause
à this
. Pour éviter les cercles, si la cause
est la this
, il renvoie null.
OMG duh. Je viens de lire cette façon de code trop vite.
Comme la question posée à l'origine est manifestement stupide, j'ai pensé à la supprimer. Au lieu de cela, je pense que je modifierai la question pour la rendre plus utile à quiconque pourrait la lire plus tard.
Je vais attribuer la "réponse" à @Persixty car cette réponse explique pourquoi cela est fait, même si bmargulies a répondu correctement à la question d'origine. La réponse de Persixty a une explication complète de la raison de cette vérification.
Cette valeur null
est utilisée par la méthode printStackTrace
, par exemple lors de l'appel de la méthode stackTraceString
, lignes 421..450 pour identifier quand la sortie de la trace de pile doit être terminée.
Le code:
public Throwable initCause(Throwable cause) { if (cause == this) //Illogical! An exception can't be self caused! throw new IllegalArgumentException(); if (this.cause != this)// false if cause has been initialised 'properly'. throw new IllegalStateException(); this.cause = cause; return this; }
Dit si la cause
est this
retourne null
sinon retourne la cause
(qui peut également être null
fur et à mesure.
L'histoire commence par ceci: private Throwable cause = this;
qui est je crois la même chose dans toutes les versions> = 1.4. Cela initialise la cause à être cet objet!
L'intention est que les objets Throwable
soient immuables mais cela fournit une méthode void initCause(Throwable cause)
qui ne peut être appelée qu'une seule fois pour initialiser la cause ou la cause initialisée par un constructeur.
Comme l'explique la documentation, cela permet le chaînage des causes à des sous-classes ajoutées avant l'introduction de la cause
qui ne l'incluent pas dans l'un de leurs constructeurs.
Ainsi, la classe veut en quelque sorte savoir si initCause
a été appelée et lève IllegalStateException
si elle a (mes commentaires):
public synchronized Throwable getCause() { return (cause==this ? null : cause); }
La classe utilise cause==this
pour indiquer la cause
non définie. Il ne peut pas utiliser cause==null
car null
est une valeur valide. Donc, il utilise l'état anormal de cause==this
parce que définir activement la cause
à this
est l'état illogique d'une exception auto-provoquée.
Cela fonctionne, bien sûr. Mais est-ce vraiment une bonne idée? Je dis non. Il confond les états «cause non définie» et «cause a été définie et est définie sur null
». Une conception moins contorsionnée introduit simplement un indicateur private boolean isCauseSet=false;
et le définir si initCause
est jamais appelé ou un constructeur qui le définit est appelé.
Le code alambiqué que nous voyons dans Throwable
ne fait rien de plus que d'éviter un boolean
. Enregistrer un seul champ boolean
dans Throwable
ne semble vraiment pas valoir la peine. Aucune application utile n'aura jamais autant d'objets Throwable
en circulation pour que cela compte.