Weird NullPointerException in Groovy inject

2019-07-07 02:54发布

groovy:000> ['homepages/gg','a','b','c','d'].inject([]) { list, conf -> if (!conf.contains('homepage')) { list << conf.trim() } }
ERROR java.lang.NullPointerException:
Cannot invoke method leftShift() on null object
        at groovysh_evaluate$_run_closure1.doCall (groovysh_evaluate:3)
groovy:000> ['homepages/gg','a','b','c','d'].inject([]) { list, conf -> conf.contains('homepage') ? list : list << conf.trim() }
===> [a, b, c, d]

Why do I get a NullPointerException in the first case but not in the second? I am using Groovy 2.3.7.

1条回答
何必那么认真
2楼-- · 2019-07-07 03:11

Because this code:

if (!conf.contains('homepage')) { list << conf.trim() }

does not return anything when the condition is not met. There's no else to this if. If you add an else returning a meaningful value, you'll avoid the exception.

The ternary operator, on the other hand, does return a value whether or not the condition is met so this code:

conf.contains('homepage') ? list : list << conf.trim()

returns an actual object even if conf does contain 'homepage'

To see why this is a problem, take a look at what the inject method does.

To quote the Javadoc:

Iterates through the given Collection, passing in the initial value to the 2-arg closure along with the first item. The result is passed back (injected) into the closure along with the second item. The new result is injected back into the closure along with the third item and so on until the entire collection has been used.

Now, let's take a look at a slightly modified example. I added a println to see what the closure parameters become:

 ['homepages/gg','a','b','c','d'].inject([]) { list, conf -> println "[$list, $conf]"; if (!conf.contains('homepage')) { list << conf.trim() } }

Running this yields:

[[], homepages/gg]
[null, a]
Exception thrown

java.lang.NullPointerException: Cannot invoke method leftShift() on null object

    at ConsoleScript9$_run_closure2.doCall(ConsoleScript9:2)

    at ConsoleScript9.run(ConsoleScript9:2)

The first call takes the empty array that you pass as the single Object parameter and the first element of the list, namely "homepages/gg". This gets passed to the if expression. Because the first element, "homepages/gg" does indeed contain the string "homepage", the if condition is evaluated to false. Because there is no else, nothing is returned.

The nothing, represented by a null reference, returned by the first evaluation of the closure is used in the second evaluation, along with the next element of the list.

conf is now equal to "a" and list is equal to null. Further on, you use the << left shift operator on the null list (list << conf.trim()).

Hence the exception.

Here's a version equivalent to the one that works:

['homepages/gg','a','b','c','d'].inject([]) { list, conf ->  if (!conf.contains('homepage')) { list << conf.trim() } else list }

The output is as expected:

===> [a, b, c, d]
查看更多
登录 后发表回答