谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Nullセーフを体感してみる。

Kotlinの一番簡単のサンプルは、これ。
世界で最もカバーされた言語数が多いと言われる、K&Rのハローワールドです。

fun main(args : Array<String>) {
  System.out?.println("Hello, world!")
}

見どころは、outの後ろについてる「?」ですね。


Kotlinは言語としてNullPointerExceptionを起こさないこと、
つまりNullセーフな性質を持っているのです。
Nullになりそうな所には「?」を記述する必要があり、
要するに、「?」は「Nullかも?」ぐらいに理解すると良さそうです。


詳しい解説は、このページにあります。
http://confluence.jetbrains.net/display/Kotlin/Null-safety
これを見ながら、サンプルを書き換えてみました。

自前でNullチェックすればいいんだよ。

まずは自分でNullチェックする方法を試してみましょう。

fun main(args : Array<String>) {
  val out : java.io.PrintStream = System.out
  if (out != null) {
    out.println("Hello, world!")
  }
}

残念ながらコンパイル通らず!(><)
こんなエラーメッセージが出ました。

(2, 35) : Type mismatch: inferred type is PrintStream? but PrintStream was expected

System.outは「PrintStream?」なのに、「PrintStream」な変数に代入しようとしている、
と怒られてしまいました。


なるほど、
「PrintStream?」つまり「Nullかも知れないPrintStream型」は、
「PrintStream」とは違う型なのだと、そういうことのようです。


では、PrintStreamに「?」を追加してみましょう。

fun main(args : Array<String>) {
  val out : java.io.PrintStream? = System.out
  if (out != null) {
    out.println("Hello, world!")
  }
}

今度は無事に実行ができて、Hello, world!がコンソールに出力されました。


そういえば、変数にはvalとvarがあるんでしたっけ。
普通に代入ができるvarと、再代入できない(final扱いの)val。


valをvarにしたらどうなるか、試してみましょう。

fun main(args : Array<String>) {
  var out : java.io.PrintStream? = System.out
  if (out != null) {
    out.println("Hello, world!")
  }
}

残念ながら、安全な呼び出しじゃないと怒られてしまいました。

(4, 8) : Only safe calls (?.) are allowed on a nullable receiver of type PrintStream?

Nullチェックをした後に、outにnullを入れられたらNPEが起きるからでしょうか?


ドキュメントを読む限りはこの書き方でも問題なさそうなのですが、
少なくとも今のデモバージョンではエラーが起きてしまいました。

「俺」が保証する、sure!

Kotlinにはsure()というメソッドも用意されています。

fun main(args : Array<String>) {
  var out : java.io.PrintStream = System.out.sure()
  out.println("Hello, world!")
}

「System.outはNullになんねぇよ、『俺』が保証するよ!」というのがsureの考え方ですね。
ただ、あまりsureを使いすぎると、せっかくのNullセーフが活かされなくなってしまいそうです。


試しにこんなコードを書いてみたら。

fun main(args : Array<String>) {
  System.out = null
  var out : java.io.PrintStream = System.out.sure()
  out.println("Hello, world!")
}

エラーが出て、開発者にバグレポートが飛んでしまいました。

Exception in Kotlin compiler: a bug was reported to developers.
Exception in thread "main" java.lang.IllegalAccessError
at namespace.main(dummy.jet:2)

開発者の人、すみません m(_ _)m

Nullかも知れない変数が、実際Nullだったらどうなるの?

ここまではNullチェックを中心に書いてきましたが、
最後に、変数がnullだった場合にどうなるのか、試してみましょう。

fun main(args : Array<String>) {
  var a : String? = null
  System.out?.println(a?.substring(0))
}

JavaならNPEが飛び出すパターンですが、
Kotlinの場合、結果はコンソールに「null」と表示されました。


Kotlinは、まずaがnullかどうかの判定を行ない、
aがnullだった場合はsubstringを実行せず、
「とりあえずnullを返す」ように振る舞うようです。


Javaな皆様方におきましては
「え、そんな勝手な振る舞いするの?」という印象をお持ちかも知れませんが、
このサンプルの場合は「a?」と自分で書いているわけで、
「aがnullだった場合は、nullが返る」ことを自分で気づくことができるのです。


ソースコードが「?」まみれになっていない限りは、ですが。

まとめ : 要するに「?」をつけないように書け。

このNullセーフな性質を見て行くと、
いちいち「?」をつけるのが面倒くさいだとか、
ソースコードが「?」まみれになって、結局ぐちゃぐちゃになりそうだとか、
sureしすぎて結局NPEが発生してしまうだとか、
そういう問題が起きそうなことは、容易に想像できてしまいます。


しかし、それはいずれもバッドプラクティスであり、
きちんと自前でNullチェックを行ない、できる限り「?」をつけずにコードを書くというのが
Kotlinの正しい使い方なのかな、と思いました。


要するに、

      ,,、,、、,,,';i;'i,}、,、
       ヾ、'i,';||i !} 'i, ゙〃
        ゙、';|i,!  'i i"i,       、__人_从_人__/し、_人_入
         `、||i |i i l|,      、_)
          ',||i }i | ;,〃,,     _) 「?」は消毒だ〜っ!!
          .}.|||| | ! l-'~、ミ    `)
         ,<.}||| il/,‐'liヾ;;ミ   '´⌒V^'^Y⌒V^V⌒W^Y⌒
        .{/゙'、}|||//  .i| };;;ミ
        Y,;-   ー、  .i|,];;彡
        iil|||||liill||||||||li!=H;;;ミミ
        {  く;ァソ  '';;,;'' ゙};;彡ミ
         ゙i [`'''~ヾ. ''~ ||^!,彡ミ   _,,__
          ゙i }~~ } ';;:;li, ゙iミミミ=三=-;;;;;;;;;''
,,,,-‐‐''''''} ̄~フハ,“二゙´ ,;/;;'_,;,7''~~,-''::;;;;;;;;;;;;;'',,=''
 ;;;;;;;;''''/_  / | | `ー-‐'´_,,,-',,r'~`ヽ';;:;;;;;;;, '';;;-'''
'''''  ,r'~ `V ヽニニニ二、-'{ 十 )__;;;;/

ってことですね?