When is InvocationTargetException.getCause() null?

2019-04-21 12:39发布

问题:

As per the javadocs, InvocationTargetException.getCause() can be null:

Returns the cause of this exception (the thrown target exception, which may be null).

But the documentation also says that it wraps an existing exception:

InvocationTargetException is a checked exception that wraps an exception thrown by an invoked method or constructor.

So it seems to me that InvocationTargetException.getCause() can never be null.

Am I missing something?

UPDATE

Yes, I missed something -- the default constructor of InvocationTargetException would cause getCause() to be null.

The question I have now is why provide a default constructor for this class at all. Is there a usecase where the exception needs to be thrown with a null cause?

回答1:

InvocationTargetException extends ReflectiveOperationException which states

Common superclass of exceptions thrown by reflective operations in core reflection.

When you use reflection to call a method (or constructor).

Method method = ...
method.invoke(instance, ...);

If the method threw an exception, it would be stored in the target field of InvocationTargetException. That'll happen in most reflection cases.

The fact that there is an empty constructor leads me to believe it might be used differently in other cases.

JDK 7

private Throwable target;

/**
 * Constructs an {@code InvocationTargetException} with
 * {@code null} as the target exception.
 */
protected InvocationTargetException() {
    super((Throwable)null);  // Disallow initCause
}


回答2:

So.

@xtravar's comment on my (much) earlier answer has caused be to have another look at this issue. And I might just got it. Please bear with me.

InvocationTargetException was introduced to Java very early. At least as early as some JDK 1.1.X which dates back to anywhere between February 1997 to December 1998. How do I know it's that old? After all, there's no @since 1.1 mark on the class.
I happened to see the following javadoc on the serialVersionUID field:

/**
 * Use serialVersionUID from JDK 1.1.X for interoperability
 */

Right. So what?
So, according to docs of Throwable.getCause(), which InvocationTargetException eventually inherits:

While it is typically unnecessary to override this method, a subclass can
override it to return a cause set by some other means. This is appropriate
for a "legacy chained throwable" that predates the addition of chained
exceptions to Throwable.
...
@since 1.4

Now, please combine this with the following note on InvocationTargetException class docs:

As of release 1.4, this exception has been retrofitted to conform to
the general purpose exception-chaining mechanism.  The "target exception"
that is provided at construction time and accessed via the
getTargetException() method is now known as the cause,
and may be accessed via the Throwable.getCause() method,
as well as the aforementioned "legacy method."

See where I'm getting at?
The note on Throwable.getCause() is aimed exactly at InvocationTargetException (for the least). InvocationTargetExcpetion was used to chain exceptions before exception-chaining was introduced to Throwable...

And when InvocationTargetException was retrofitted, as noted, I assume that the language designers wanted to:

  1. prevent the possibility of InvocationTargetException having two different "causes" - one stored in target and the other in cause; and still
  2. be backward-compatible with existing code that relies on the target field.

That's why they left the target field as the one that's really used and implemented any existing constructors so that the cause field remains null for good. The implementation of getCause() for InvocationTargetException, of course, returns target as the cause.

So to answer

Is there a usecase where the exception needs to be thrown with a null cause?

Not really, it's not how the class is - and was - intended to be used.

And yet, this question remains:

why provide a default constructor for this class at all

(and this constructor seems to exist ever since)

I tend to think that this class is actually quite null-tolerant. After all, the public constructors permit null as the Throwable target. As a designer, if you already permitted that, you may as well add the protected default constructor that explicitly assigns null to target, enabling inheriting classes to construct the class however needed.

This also answers the original question:

So it seems to me that InvocationTargetException.getCause() can never be null.

Am I missing something?

Yes. InvocationTargetException is indeed intended to have a non-null target and cause. However, what's "missed" here is that target (and hence cause) unfortunately can be null, as nothing in the class forces it otherwise.



回答3:

I agree with you -- I don't see how InvocationTargetException can ever be null, because it's only thrown when the target throws an exception.

My guess is that the text for getCause() that states "which may be null" is just boilerplate copied from the Throwable.getCause() Javadoc.



回答4:

First, its worth mentioning that not only null cause is permitted via the protected constructor. You can also invoke the public construcors with target valued to null. No NullPointerException will occur...

Having said that, it does seem like more than something that the designers just slipped.

Could be that the designers wanted to enable instantiating the class via reflection, as in:

Constructor ctor = // ... some code to get the no-arg constructor that I spared here
ctor.setAccessible(true); // it's protected constructor...
InvocationTargetException e = (InvocationTargetException) ctor.newInstance();

That way, util method can create InvocationTargetException instances and pass them to client code that adds up the particular cause.