Let's consider two somewhat contrived implementations of checking nullity of a field dereference chain in Scala
and Java
. Note that the use of Try
below is an anti-pattern because it's essentially equivalent to using try/catch
in Java
as a nullity check. We use it for illustrative purposes since its idiomatic use-case involves case matching.
import scala.util._
class X {
val x = new X
val y: X = null
Try(x.y.x) match {
case Success(x) => println(x)
case Failure(x) => println("null")
}
}
class X {
X x = new X();
X y = null;
X() {
if (x != null && x.y != null) {
System.out.println(x.y.x);
} else {
System.out.println("null");
}
}
}
Below are source file sizes. Scala wins by whole 3-bytes, game on.
166 May 11 12:58 X.scala
169 May 11 12:55 X.java
Result of javac X.java
,
530 May 11 12:55 X.class
Result of scalac X.scala
,
858 May 11 12:46 X$$anonfun$1.class
1484 May 11 12:46 X.class
855 May 11 12:17 X$$anonfun$2.class
From the above, scalac
generated 3197 bytes worth of bytecodes, around 6x increase from 530 bytes generated by javac
.
Let's compare the bytecodes for the main block of code. javac
's version is 64 lines whereas scalac
's version is 112 lines, almost double. Note that scala
's version includes instructions like instanceof
and checkcast
which are used to implement case-matching.
X();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class X
8: dup
9: invokespecial #3 // Method "<init>":()V
12: putfield #4 // Field x:LX;
15: aload_0
16: aconst_null
17: putfield #5 // Field y:LX;
20: aload_0
21: getfield #4 // Field x:LX;
24: ifnull 56
27: aload_0
28: getfield #4 // Field x:LX;
31: getfield #5 // Field y:LX;
34: ifnull 56
37: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
40: aload_0
41: getfield #4 // Field x:LX;
44: getfield #5 // Field y:LX;
47: getfield #4 // Field x:LX;
50: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
53: goto 64
56: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
59: ldc #8 // String null
61: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
64: return
public X();
Code:
0: aload_0
1: invokespecial #21 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class X
8: dup
9: invokespecial #22 // Method "<init>":()V
12: putfield #14 // Field x:LX;
15: aload_0
16: aconst_null
17: putfield #17 // Field y:LX;
20: getstatic #28 // Field scala/util/Try$.MODULE$:Lscala/util/Try$;
23: new #30 // class X$$anonfun$1
26: dup
27: aload_0
28: invokespecial #33 // Method X$$anonfun$1."<init>":(LX;)V
31: invokevirtual #37 // Method scala/util/Try$.apply:(Lscala/Function0;)Lscala/util/Try;
34: astore_1
35: aload_1
36: instanceof #39 // class scala/util/Success
39: ifeq 70
42: aload_1
43: checkcast #39 // class scala/util/Success
46: astore_2
47: aload_2
48: invokevirtual #43 // Method scala/util/Success.value:()Ljava/lang/Object;
51: checkcast #2 // class X
54: astore_3
55: getstatic #48 // Field scala/Predef$.MODULE$:Lscala/Predef$;
58: aload_3
59: invokevirtual #52 // Method scala/Predef$.println:(Ljava/lang/Object;)V
62: getstatic #58 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
65: astore 4
67: goto 103
70: aload_1
71: instanceof #60 // class scala/util/Failure
74: ifeq 104
77: aload_1
78: checkcast #60 // class scala/util/Failure
81: astore 5
83: aload 5
85: invokevirtual #64 // Method scala/util/Failure.exception:()Ljava/lang/Throwable;
88: astore 6
90: getstatic #48 // Field scala/Predef$.MODULE$:Lscala/Predef$;
93: aload 6
95: invokevirtual #52 // Method scala/Predef$.println:(Ljava/lang/Object;)V
98: getstatic #58 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
101: astore 4
103: return
104: new #66 // class scala/MatchError
107: dup
108: aload_1
109: invokespecial #68 // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
112: athrow
Kotlin beats both Java
and Scala
with respect to null handling. Both the source code and the bytecode are most concise.
class X {
val x: X? = X()
val y: X? = null
init {
println(x?.y?.x? : "null")
}
}
public X();
Code:
0: aload_0
1: invokespecial #20 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class X
8: dup
9: invokespecial #21 // Method "<init>":()V
12: putfield #11 // Field x:LX;
15: aload_0
16: getfield #11 // Field x:LX;
19: dup
20: ifnull 40
23: getfield #16 // Field y:LX;
26: dup
27: ifnull 40
30: getfield #11 // Field x:LX;
33: dup
34: ifnull 40
37: goto 43
40: pop
41: ldc #23 // String null
43: astore_1
44: getstatic #29 // Field java/lang/System.out:Ljava/io/PrintStream;
47: aload_1
48: invokevirtual #35 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
51: return