injoy
injoy

Reputation: 4383

Why this Scala code returns NullPointerException?

The code is:

def mergeTrees(t1: TreeNode, t2: TreeNode): TreeNode = {
        if (t1 == null && t2 == null) null
        else if (t1 == null) t2
        else if (t2 == null) t1

        val root = new TreeNode(t1.value + t2.value)
        root.left = mergeTrees(t1.left, t2.left)
        root.right = mergeTrees(t1.right, t2.right)

        root
}

And if I change it to:

def mergeTrees(t1: TreeNode, t2: TreeNode): TreeNode = {
        if (t1 == null && t2 == null) null
        else if (t1 == null) t2
        else if (t2 == null) t1
        else {
            val root = new TreeNode(t1.value + t2.value)
            root.left = mergeTrees(t1.left, t2.left)
            root.right = mergeTrees(t1.right, t2.right)

            root
        }
}

Then it works. What's the reason behind this?

Upvotes: 1

Views: 61

Answers (3)

sarveshseri
sarveshseri

Reputation: 13985

In Scala language specification, the syntax for def is supported by following gremmar,

Def                ::=  ‘def’ FunDef
FunDef             ::=  FunSig [‘:’ Type] ‘=’ Expr
FunSig             ::=  id [FunTypeParamClause] ParamClauses

So, in your def mergeTrees(t1: TreeNode, t2: TreeNode): TreeNode = {...}, the RHS is an Expr and this particular syntax for Expr is supported by following grammar,

Expr               := Expr1
Expr1              := PostfixExpr
PostfixExpr        := InfixExpr [id [nl]]
InfixExpr          := [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr
SimpleExpr         := BlockExpr
BlockExpr          := ‘{’ Block ‘}’
Block              ::=  BlockStat {semi BlockStat} [ResultExpr]

So... the parser determines your RHS to be a BlockExpr,

And if you read about blocks in Scala language specification at - https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#blocks, you will find the following explantion,

A block expression {s1s1; ……; snsn; ee} is constructed from a sequence of block statements s1,…,sns1,…,sn and a final expression e.

Now, what is a block statement of BlockStat?

In we just consider the following case

def abc(i: Int): Int = {
  i + 1
  i + 5
}

In this case the i + 1 will be ignored (treated as a statement) and i + 5 will be the return value.

Which is similar (but far simpler) to your case, then the RHS BlockExpr is parsed as following,

'{'
   i + 1 (BlockStat)
   i + 5 (ResultExpr)
'}'

But, that i + 1 looks like an Expr (or Expression) then why is that treated as statment, that is because of following grammar for BlockStat,

BlockStat    ::=  Import
               |  {Annotation} [‘implicit’ | ‘lazy’] Def
               |  {Annotation} {LocalModifier} TmplDef
               |  Expr1

Which means any Expr of Expr1 grammer can be also be treated as BlockStat.

So, in this case since the following,

{
  i + 1
  i + 5
}

best matches to the grammar,

'{'
  BlockStat
  ResultExpr
'}'

So, this i + 1 which is actually an Expr1 is treated as an statement and not an expression. Which means that it will not be the value of the block expression and the next i + 5 will be tread as value of the block.

Now, in your case since the following expression,

 if (t1 == null && t2 == null) null
 else if (t1 == null) t2
 else if (t2 == null) t1

is followed by more statements, these will be treated as a statement and thus it will not be return value of your def. And further expression/statements will also be evaluated.

And since your next statement is,

val root = new TreeNode(t1.value + t2.value)

t1.value or t2.value will throw NullPointerException on evaluation in case if t1 or t2 is null.

Upvotes: 1

jwvh
jwvh

Reputation: 51271

A chain of if, else if, ..., conditions constitutes a single expression with a single result.

In your 1st example the if else ... chain results in either null or t2 or t1. But the if else expression is not the only expression in the code block so, because the if else result is not saved anywhere then it is thrown away and processing moves on to the next expression. Which is not what you intended or want.

The 2nd code example works because there is only one expression in the mergeTrees() method and that is the if else expression. So in that case the result of the if else is the result of the entire method.

Upvotes: 1

dave
dave

Reputation: 4922

In your first example you have an if statement which calculates a value and then throws that value away. And then it proceeds to calculate the root assuming that neither t1 or t2 is null.

The second example adds an else clause that is only used when neither t1 or t2 are null. Now the result of the function is the result of the last statement in that function (which is the whole if statement). And those values on other branches of the if statement are useful (they might be the last value of the function and thus the return value).

Upvotes: 1

Related Questions