I have this code:
package tests;
import java.util.Hashtable;
public class Tests {
public static void main(String[] args) {
Hashtable<String, Boolean> modifiedItems = new Hashtable<String, Boolean>();
System.out.println("TEST 1");
System.out.println(modifiedItems.get("item1")); // Prints null
System.out.println("TEST 2");
System.out.println(modifiedItems.get("item1") == null); // Prints true
System.out.println("TEST 3");
System.out.println(Boolean.valueOf(null)); // Prints false
System.out.println("TEST 4");
System.out.println(Boolean.valueOf(modifiedItems.get("item1"))); // Produces NullPointerException
System.out.println("FINISHED!"); // Never executed
}
}
My problem is that I don't understand why Test 3 works fine (it prints false
and doesn't produce NullPointerException
) meanwhile Test 4 throws a NullPointerException
. As you can see in tests 1 and 2, null
and modifiedItems.get("item1")
are equals and null
.
The behavior is the same in Java 7 and 8.
You've got to look carefully at which overload is being invoked:
Boolean.valueOf(null)
is invoking Boolean.valueOf(String)
. This doesn't throw an NPE
even if supplied with a null parameter.Boolean.valueOf(modifiedItems.get("item1"))
is invoking Boolean.valueOf(boolean)
, because modifiedItems
's values are of type Boolean
, which requires an unboxing conversion. Since modifiedItems.get("item1")
is null
, it is the unboxing of that value - not the Boolean.valueOf(...)
- which throws the NPE.The rules for determining which overload is invoked are pretty hairy, but they roughly go like this:
In a first pass, a method match is searched for without allowing boxing/unboxing (nor variable arity methods).
null
is an acceptable value for a String
but not boolean
, Boolean.valueOf(null)
is matched to Boolean.valueOf(String)
in this pass;Boolean
isn't an acceptable for either Boolean.valueOf(String)
or Boolean.valueOf(boolean)
, so no method is matched in this pass for Boolean.valueOf(modifiedItems.get("item1"))
.In a second pass, a method match is searched for, allowing boxing/unboxing (but still not variable arity methods).
Boolean
can be unboxed to boolean
, so Boolean.valueOf(boolean)
is matched for Boolean.valueOf(modifiedItems.get("item1"))
in this pass; but an unboxing conversion has to be inserted by the compiler to invoke it: Boolean.valueOf(modifiedItems.get("item1").booleanValue())
(There's a third pass allowing for variable arity methods, but that's not relevant here, as the first two passes matched these cases)