Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala overriding def with val throws NPE

Tags:

scala

I am learning Scala and in Programming in Scala 3rd Ed, Ch 10, Page 225, section Overriding methods and fields, it says

The uniform access principle is just one aspect where Scala treats fields and methods more uniformly than Java. Another difference is that in Scala, fields and methods belong to the same namespace. This makes it possible for a field to override a parameterless method. For instance, you could change the implementation of contents in class ArrayElement from a method to a field without having to modify the abstract method definition of contents in class Element, as shown in Listing 10.4:

My code based on the example is

with def

abstract class Element {
  def contents: Array[String]

  val height = contents.length

  val width = if (height == 0) 0 else contents(0).length
}


class ArrayElement(contnts: Array[String]) extends Element {
  def contents: Array[String] = contnts
}

// --
val ae = new ArrayElement(Array("hello", "world"))
ae.height
ae.width

I get

ae: ArrayElement = ArrayElement@7cd3ba8e
res0: Int = 2
res1: Int = 5

with def overridden as val in ArrayElement

abstract class Element {
  def contents: Array[String]

  val height = contents.length

  val width = if (height == 0) 0 else contents(0).length
}


class ArrayElement(contnts: Array[String]) extends Element {
  val contents: Array[String] = contnts
}

// --
val ae = new ArrayElement(Array("hello", "world"))
ae.height
ae.width

I get NPE as

java.lang.NullPointerException
    at #worksheet#.Element.<init>(scratch.scala:4)
    at #worksheet#.ArrayElement.<init>(scratch.scala:10)
    at #worksheet#.ae$lzycompute(scratch.scala:15)
    at #worksheet#.ae(scratch.scala:15)
    at #worksheet#.#worksheet#(scratch.scala:14)

What am I missing?

like image 409
daydreamer Avatar asked May 27 '16 14:05

daydreamer


1 Answers

Class-level fields are initialized before anything else, meaning that null is assigned. You can make the declaration a lazy val and it won't be initialized until it's called. That's the reason the def works. A better way though, instead of creating a class public field shadowing the private constructor field, is to just make the constructor field public like this:

class ArrayElement(val contnts: Array[String]) extends Element {}

Since there's a parent class at play here too, it would be good to mark it as overriding;

class ArrayElement(override val contnts: Array[String]) extends Element {}

If this is going to be a stateless data container class though, the best option is to make it a case class, which (among several other things) has public-by-default fields.

case class ArrayElement(override val contnts: Array[String]) extends Element

This is much more idiomatic scala and it will provide you with a value-based equals, hashCode, pattern matching, simpler construction (no need to new)

like image 78
Daenyth Avatar answered Oct 22 '22 07:10

Daenyth