1 of 69

Past & Future of null in Java

JJUG CCC 2015 Spring #CCC_CD1

2 of 69

3 of 69

Javaの鉱脈 連載中

4 of 69

Siden

https://github.com/taichi/siden

5 of 69

今日の目標

6 of 69

nullについて考え直すきっかけを提供したい

7 of 69

NullPointerException(NPE)をみたくない

null が無ければNPEも無いはずだ

そうだ null、 撲滅しよう。

8 of 69

null とは?

9 of 69

cc-by-sa-2.0-fr

Null References: The Billion Dollar Mistake

10 of 69

NULL参照の起源

ALGOL 60の後継言語であるALGOL 68へのプロポーザルとして幾つかの言語が設計された。

ホーア先生とヴィルト先生はオブジェクト指向言語であるALGOL Wを設計した。

コンパイラによって参照が安全である事を保証する仕組みを設計する過程で安易にNULL参照を導入してしまった。

ALGOL WはALGOL 68として採択されなかったが、その後、ヴィルト先生の設計したPascalではポインタ型の一部としてnil ポインタが採用された。

11 of 69

Javaにおけるnullとは

12 of 69

  • nullはリテラル
  • null型は唯一の値としてnull参照を持つ
  • null型の変数は定義できない
  • null型にキャストできない
  • null参照はあらゆる型にキャストできる

13 of 69

Java以外のnull

14 of 69

Ruby

  • nilはキーワード
  • nilはNilClassのシングルトンインスタンス
  • 「何もないこと」を表すためにnilが使われる
  • 未初期化のメンバ変数は nil
    • 未初期化の変数は基本的に作れない
  • 制御構文の多くがある種の条件で nil を返す
  • nil は false とほぼ同じ振る舞いをする
  • Objectのnil?メソッドはレシーバがnilの時trueを返す
  • nilには無いメンバにアクセスしたり、メソッドを呼び出すとランタイムエラーになる

15 of 69

JavaScript

  • null はリテラル
  • 「何もないこと」を表すためにnullが使われる
  • undefinedはグローバルオブジェクトの属性
  • 未初期化の変数は undefined になる
  • 値を返さない関数の戻り値は undefined
  • nullとundefinedは特別な値である
  • nullとundefinedは一切のメンバがない
  • nullとnudefinedは関数として呼び出そうとするとランタイムエラーになる

16 of 69

Go

  • ポインタ、関数、インターフェース、スライス、チャンネル、マップのゼロ値がnil
  • 「何もないこと」を表すためにnilが使われる
  • nilには型がある
  • nil状態の変数をレシーバに関数を呼出せる
  • nil状態の変数のメンバ変数にアクセスするとランタイムエラーになる

17 of 69

Scala

  • null はNull型のシングルトンインスタンス
    • Null型は全ての参照型のサブクラスである
  • Nothing型は値が無いことを表す型なのでインスタンスがない
  • Nothing型は全ての型のサブクラスである
  • 要素の無いListがNil型

  • 値があるかもしれないことを表すOption型において値が無い場合がNone型

18 of 69

SQL(RDB)

  • 三値論理を採用しているのでtrue、falseの他にunknownがある
  • NULLは未知(UNKNOWN)もしくは適用不能(Not Applicable)を表す
  • NULLは値ではない
    • 何かがNULLであるか調べるには IS NULL を使う

NOT

TRUE

F

FALSE

T

UNKNOWN

U

AND

T

F

U

TRUE

T

F

U

FALSE

F

F

U

UNKNOWN

U

U

U

OR

T

F

U

TRUE

T

T

T

FALSE

T

F

U

UNKNOWN

T

U

U

19 of 69

Javaのnullを相対的に理解する

  • 「未初期化」と「何もないこと」は区別できなくてもよさそう
  • nullが特別な値だと扱い易いかもしれない
  • Scalaにおける「何もないもの」の分類はやり過ぎ感はあるものの参考になる
  • Goにおける型付けされたnilという考え方は参考になる

20 of 69

NPEは何故起きるのか?

21 of 69

言語仕様の落とし穴

ライブラリの落とし穴

設計における考慮不足

22 of 69

言語仕様の落とし穴

オートボクシング

NullPointerException

23 of 69

言語仕様の落とし穴

拡張forループ

NullPointerException

24 of 69

言語仕様の落とし穴

synchronized

NullPointerException

25 of 69

言語仕様の落とし穴

throw

NullPointerException

26 of 69

言語仕様の落とし穴

try-with-resources

OK

27 of 69

言語仕様の落とし穴

発生するケースを覚えてしまえばよい

正直、コンパイラにチェックして欲しい

28 of 69

ライブラリの落とし穴

java.util.Comparator

NullPointerException

29 of 69

ライブラリの落とし穴

java.util.NavigableSet

NullPointerException

30 of 69

ライブラリの落とし穴

SpringのComponentScan

31 of 69

ライブラリの落とし穴

CDIのbean-discovery-modeとかscanとか

32 of 69

ライブラリの落とし穴

各自頑張って下さいとしか言いようがない

33 of 69

設計における考慮不足

34 of 69

「何もないこと」について考え直そう

35 of 69

「何もない」を値で表すと扱い易くなるかもしれない

「メソッドの有効な戻り値がない」時、呼び出し側にできることは無いだろうか?

「有効な値を返さないかもしれない」メソッドを効率よく、合理的に調べる方法は無いだろうか?

型のあるnullは作れないだろうか?

無効な値でメソッドを呼び出されないようにしたい

36 of 69

「何もない値」を表現する方法

37 of 69

空のオブジェクトを使う

  • 長さが0のString
  • 長さが0の配列
  • 長さが0のCollection
    • Collections#emptyIterator
    • Collections#emptyList
    • Collections#emptyMap
    • Collections#emptySet
    • ……

38 of 69

型のあるnullを表現する方法

39 of 69

NullObjectパターン

40 of 69

「有効な値を返さないかもしれない」メソッド

41 of 69

java.util.Optional<T>

  • Optionalが戻り値の型として宣言されているメソッドは有効な値を返さないかもしれない
  • Optionalによって「何もない」値を扱える
    • nullよりは扱い易いかもしれない
  • Optional#emptyは汎用的なNullObjectを返すメソッドであるとみなせる

42 of 69

java.util.Optional<T>

  • Optionalを戻り値としたメソッドは決してnullを返すべきでないが、それを防ぐ方法はない
    • 未熟な者や無法者はどこにでもいる
  • Optionalがラップしている型をリフクレションで調べるのは極めて困難である
  • OptionalはSerializableでないのでメンバ変数に使うと問題を引き起こす
  • Javaにパターンマッチが無いのでScalaのOption型よりも解決できる問題の量は少ない

43 of 69

メソッドの戻り値が無効な時、呼び出し側にできること

44 of 69

あらかじめ代替となる値を渡す

java.util.Map#getOrDefault

java.util.Objects#toString

45 of 69

戻り値の状態に併せて処理を行う

伝統的なnullチェック

46 of 69

戻り値の状態に併せて処理を行う

Optionalの誤った使い方

47 of 69

何が誤っているのか

Optionalを使っているが処理構造に変化が無い

Optionalを使わない時よりもコードが増えている

問題を解決するどころか問題を増やしている

48 of 69

戻り値の状態に併せて処理を行う

Optional#ifPresentで値がある時だけ処理する

49 of 69

戻り値の状態に併せて処理を行う

Optional#orElseで代替の値を指定する

50 of 69

戻り値の状態に併せて処理を行う

OptionalInt#orElseで代替の値を指定する

プリミティブ型で「無効な値」を扱えるようになった

51 of 69

戻り値の状態に併せて処理を行う

Optional#filterで値の有効性を後から決める

52 of 69

戻り値の状態に併せて処理を行う

Optional#mapで有効な値を変換する

53 of 69

戻り値の状態に併せて処理を行う

Optional#mapはnullで値を無効化できる

54 of 69

戻り値の状態に併せて処理を行う

沢山の猫が入った箱があるとする

55 of 69

戻り値の状態に併せて処理を行う

Optional#mapを使うとOptionalが入れ子になる

56 of 69

戻り値の状態に併せて処理を行う

Optional#flatMapでOptionalの入れ子を防ぐ

57 of 69

戻り値の状態に併せて処理を行う

Optional#flatMapでnullを返してはいけない

58 of 69

無効な値でメソッドを呼び出されないために

59 of 69

JSR305やIDE組込みの@NonNull を使う

アノテーションは強制力が弱いが影響も少ない

コンパイラやIDEの力を借りる

Checker Frameworkなら更に高度な解析ができる

60 of 69

@NonNullで言語仕様の落とし穴を防ぐ

61 of 69

要素にnullが含まれないことを宣言する

62 of 69

nullを拒絶する

java.util.Objects#requireNonNull

com.google.common.base.Preconditions#checkNotNull

早期にインターフェースエラーを発見するのが目的

この方針ならユニットテストを潤沢に用意すること

assert は歴史の闇に消えた

63 of 69

Java7に入りそうで入らなかった機能

64 of 69

  • Groovy
  • C# 6
  • Swift

65 of 69

66 of 69

最後に

67 of 69

Javaには巨大な資産がある

  • 巨大な標準API
  • 沢山のOSSライブラリ
  • 構築済のシステム
  • 有用な資産はちゃんと使おう

68 of 69

ゆるやかによりよい形へ移行しよう

  • 新しいものが無くてもやれる事はある
  • 合意できるやり方を選ぼう
  • コントローラブルなリスクを取ろう

69 of 69

ご清聴ありがとうございました