Any time the dictionary does not contain s
(wordDict.contains(s)
is false
), the second condition (mem.get(s) == true
) is evaluated.
In Java's library Maps, attempting to obtain the value for a key which isn't present returns null
. So every time that key isn't in the mem
map, null
is returned, and compared (using ==
) with true
. When the Boolean
type gets compared with, or assigned to, a boolean
value, it is 'autounboxed'. This means the Boolean.booleanValue()
method is called. If the Boolean
is null
, this is what causes the exception, because it means calling null.booleanValue()
. null
is not anything, so it doesn't know how to be a boolean!
The wrapper classes are useful constructs which enable the primitive types to interoperate with types inheriting from Object
, that is, reference types (everything else). When you deal with primitive types ('value' types), their values are directly present in the location they're described in - either as part of the memory being used to execute the current function (for local variables) or as part of the memory space allocated to an object in its memory space (for field variables). When you deal with reference types (those inheriting from Object
, including Boolean
and the other wrapper types), the data being referred to instead exists in a memory space called the Heap. Alongside this Heap memory allocation, in order to know where that object is, the entity analogous to the value for value types is in fact a reference to the memory location of the object, stored as a local variable or a field variable, not the value or data of the object itself. This is what enables these to be null
: a null
reference says that this variable points to nothing in particular. (Read about Stack and Heap allocation for more detail.)
The reason you're safe comparing to Boolean.TRUE
is because on Object
types, like Boolean
(and any of the wrapper classes for primitive types), the variable of type Boolean
is in fact a reference. This means the ==
operator is actually checking if the references are the same - i.e. if the actual object in memory is the same one (has the same memory location), not if they are 'equal' by some value-based definition of 'equal'. You don't want this, because you can get surprising results like new Boolean(true) == new Boolean(true)
returning false. This is incidentally why we have the equals
method - this should be defined by any class for an object that wants to be compared by value rather than by reference. On the other hand, for the primitive value types, like boolean
, the ==
operator literally compares the value. This is why it's useful to have the wrapper types box and unbox automatically - a Boolean
variable (without being 'dereferenced' - finding the value it points to) is actually a reference value, referring to a memory location. A boolean
variable is an actual boolean value. Hence, without unboxing and boxing automagically, it would make no sense to try to compare the two.
If you want to make sure that the value of mem.get(s)
is neither null
, nor false
, use something like mem.containsKey(s) && mem.get(s) == true
. The first check ensures that there will be no null reference.