Consider:
object implicitnull extends App {
mymethod
implicit val arg = "foo"
def mymethod(implicit arg: String) = {
arg.size
}
}
This does not cause any compilation error, however, at runtime results in NullPointerException
coming from arg.size
.
Is this the intended behavior?
Yes it's the intended behavior due to the way Scala constructs classes and initializes them. Consider this example:
scala> class A {
| f
|
| implicit val arg = "foo"
|
| def f(implicit arg: String) = {
| println(arg)
| }
| }
defined class A
scala> class B {
| f(arg)
|
| val arg = "foo"
|
| def f(implicit arg: String) = {
| println(arg)
| }
| }
defined class B
scala> class C {
| implicit val arg = "foo"
|
| f
|
| def f(implicit arg: String) = {
| println(arg)
| }
| }
defined class C
scala> new A
null
res0: A = A@67d3caf
scala> new B
null
res1: B = B@3f2c5ad4
scala> new C
foo
res2: C = C@177bdd23
At the moment the function f
is invoked in class C
the value has been initialized, while in class B
it has been not initialized yet. Class A
is exactly the same as class B
- the only difference is that Scala passes the arg
implicitly in A
.
It's a little bit confusing because this code is doing 2 things - it's declaring member variables and executing constructor code. If you take class B
for example, val arg
would be declared at the point where f
is called, but not initialized yet. val arg = "foo"
does the initialization. Once you translate this to Java it becomes more obvious:
public class B {
void f(String arg) {
System.out.println(arg);
}
String arg; // also acts as final in Scala
public B() {
f(arg);
arg = "foo";
}
}
Using lazy val
or correct initialization order will help to fix it:
scala> class B {
| f(arg)
|
| lazy val arg = "foo"
|
| def f(implicit arg: String) = {
| println(arg)
| }
| }
defined class B
scala> new B
foo
res3: B = B@3f9ac6e6
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With