19.0430

NullPointerException(ナル・ポインター・エクセプション)

NullPointerException とは、ちまたでは 「ぬるぽ」 と言われている 例外 です。
確かに、
 ArrayIndexOutOfBoundsException/アレイ・インデックス・アウト・オブ・バウンズ・エクセプション
よりは、短いですが、いちいち全部言うのは長いですね。

そもそも、null とは、何なのか?

null とは、全く何もデータがない!状態だと言えます。

ここで、気を付けるのは、""(空文字)は、Stringリテラル ですが、
  実は、null では、ありません!
プログラミング的には、立派に、Stringインスタンス なのです。
こんなコードで、確認してみましょう。
	fun main() {
	    val nothing: String = ""
	    println(nothing is String)
	}
結果は、true なので、Stringインスタンス だと言えます。


NullPointerException は、とても重要なところですので、
公式リファレンス を、参照したいと思います。
以下、Kotlinの公式リファレンス にて、「null安全性について」 に関する、ページ を、訳してみました。
全力で訳しましたが、必ず誤訳がないとは、言いきれませんので、ご了承ください。
なお、日本語のページ に関しては、まだ訳が進んでおりませんが、こちらになります。

リファレンス対応の訳は、こちらです



NullPointerException
NPE/NullPointerException とは、アクセスしようとした値が、null だった!
という事態に遭遇した時、発生する(throw/投げる とよく言われます)、例外 です。
null に、メソッド や プロパティ に、アクセスしろと言われても、
「無」では、アクセスできません
という事です。
  イメージ:[無].メソッド/プロパティ
これは、無理に決まっていますね。

null許容型非null許容型
null許容型 とは、
その値 は、null であっても代入できる ことを宣言した
型 のことです。
それに対して、非null許容型 とは、
簡単に言えば、いわゆる「普通の型」 で、
  null は、代入できません!

これを踏まえて、右の、リファレンス を、見ていきましょう。

まずは、最初の、 ピンクの枠のコード は、こんなコードです。

fun main() { var a: String = "abc" a = null // compilation error(コンパイルエラー) }
このコードの意味は、
  そもそも、変数 に、null は、代入できません!
という事を、言っています。

いや、実は、null なんて使ったこともないので、
一応、そんなに、特別なことなのかと思って、
IntelliJ で、Java も使えるので、試してみました。

// Java では、null を、代入できる. String str = "Java"; str = null;
確かに、Java では、null を、代入できます(警告を出さなかったので)。
そういうことです。
とても簡単に、null を扱えてしまいます。
そして、null に、代入し忘れて、してあるつもりで、メソッドを呼んだら、NPE ですね。
NPE は、プログラミング言語 にとって、大きな問題であったようで、
いろいろな言語 が、対策しているようです。
それについての言及は出来ませんが(知識がないので)、
とにかく、Kotlin の、null対策に対する賛辞は、ググっているときにもよく見ます。
ですから、何はともあれ、NPE対策 をマスターしましょう。

先に述べた、リファレンスの日本語訳ですが、
そのページのイントロ部分は、まあ、はしょってもいいなと思って スキップ/ある部分を飛ばす しました。
それに、全然分からない話もありましたので。
(グーグルクロム で、右クリックから自動翻訳をかけても、おおよそ、書いてあることは、分かります)

また、NPE とは、密接に関係ない話もありましたが、null に、まつわる話で、
いずれ覚えた方がいいと思ったので、最初の飛ばしたところ以外は、
「Kotlin イン アクション」 を読んだりして、何とか理解して、訳しました。
その部分についても、ここで触れていこうと思います。
「Kotlin イン アクション」 とは、Kotlin の、作者の一人である、
Dmitry Jemerov / ドミトリー ジェメロフ 氏 も、携わっている、今のところ、
Kotlin言語 の、バイブル とも言えそうな本だと思われます。
この本は、必要な部分を少し読んだだけですが、それほど堅苦しくなく、
割と読みやすいのではないかと思われます。
翻訳精度も、高いと言えると思われます(完璧かも?)。
(ただ、Java の知識がないと、難しそうですが、、ある程度プログラミング経験があれば、
 察しながら読めるかもしれません。まだ何とも言えません。)


話を戻しまして、続きを見ていきましょう。

fun main() { var b: String? = "abc" b = null // ok print(b) }
先に話しました、null許容型 についての、
具体的なコード例です。
2行目:
  変数b が、String? となっていますが、
  この、型の後ろに付ける ? こそが、
  null許容型 の、印 です。
    AruType?
    (ある型?
  こんな風にして、非null許容型(普通の型)を、
  null許容型 に、変換します。
3行目:
  null許容型String に、null を、代入しています。
  もちろん、許容型ですので、OKです。
4行目:
  print()関数 で、変数b を、コンソールに、出力です。
  null が、出力されます。


println()関数 は、何を出力しているのか? とにかく、ひんぱんに使う println()関数 は、何を、出力しているのでしょうか? 何をと言われても、言わずもがなと思われるかもしれませんが、 これは知っておいた方が、変な違和感を時々感じることがなくなるような気がします。 私の場合は、最初、「リテラル表現」 を、返すのかと思っていましたが、 (本当は、その方がよいのではとは思っているのですが)   toString()関数 の、返し値 を、出力しているのです。 toString()関数 の、構文は、   fun Any?.toString(): String です。 つまり、   すべての変数が呼び出すことが出来る、関数 と言えます。   Any とは、すべての変数 の、親 になります。 // こんな、value型(値型)でも、true を、返します。 // もちろん、class型 でも、true を、返します。 fun main() { println(1 is Any) println(Car() is Any) } class Car() そして、Any に、? が付いた   Any? は、本当の意味で、   any(何であれ) に、なりますね。 これは、今の段階では、余談になりますが、 先ほどのコードで、println(Car()) などとすると、  (Car() は、変数名のない、Carクラスインスタンスですね) 例えば、   Car@30c7da1e のような、訳の分からない出力をします。 この先学習する dataクラスだと、 自動で、toString() を、override します。

以後、右のリファレンスのコード に、番号を振って(#何番目)見ていきます


 #3
ここでの 変数a は、先の、 #1  での、a のことを言っています。
a に、null は、代入できていなかったので、"abc" の、ままですね。
ですから、NPE が、起きないことは、確実です。


 #4
ここでの、変数b は、先の、 #2  での、b のことで、
b は、String?型 で、定義されているので、null が、
代入されています。
ですから、b は、プロパティlength に、アクセスできません。
いくら、b が、null許容型 であっても、呼び出すことは、話が別なのです。
後で見ますが、「安全呼び出し」 しないと、このコードは、コンパイルエラー です。
ですが、普通は、NPE が起きるよりは、よいはずです。


 #5
このコードで言いたいのは、
まず、
  if(b != null)
つまり、
  もし、変数b が、null でないのならば、
  変数b の、プロパティ length に、アクセスする
というコードですね。
これは、プログラマ から見れば、NPE なんか起こすわけはありません。
言いたいのは、そこではなく、
  Kotlin の、コンパイラー が、
  コードの、文脈を読んで

チェックをしている、という事なのです。
そして、length へのアクセス を、許可しています。
この、コンパイラ の、仕事ぶりが、素晴らしいのだ、、と言いたいわけです。
(くどいですが、コードの文脈を読んで、コンパイラ は、チェックしていること)


 #6
ここで、言いたいことも、先ほど述べたことと同じことです、
ただ、
  複雑なコードだって、Kotlin の コンパイラ は、
  素晴らしい仕事をするのだ
と言いたい、わけです。

また、ここも、大事なことですが、
  単に、if文 の中で OK だとしても、
  if文 の、外に出たら、
  b が、null になることは、あり得ますから
  その場合は、コンパイルが通るとは、限らない
という事です。
ですから、「なぜ、コンパイルできないのだ?」 の時には、
コード全体を、見直す必要があります。
(IDEの機能 や スタックトレース を見れば、解決できるでしょう)


 #7
このコードでは、
  安全呼び出し(Safe calls)
についての話です。
ここでの、変数b は、null許容型のString(String?)です。
ですから、変数b からは、プロパティ には、アクセス出来ません。
アクセスを許すと、NPE が、起こるかもしれません。
でも、どうしてもアクセスする必要がある時には、
  ?.
  を使うと、アクセスできる
という手段があります。
このコードでは、null許容型String b から、
  b?.length
で、文字列b の(null かもしれないが/いやnullですが)length プロパティ に、アクセスしています。
その結果を、println() の、引数として返していますが、
  null
を、返しています。
そうです、
  「安全呼び出し」 では、null であった時には、null を、返す
のです。
そして、それは多くの場合、NPE よりかはましという事です。


 #8
ここは、日本語訳が間違っていたので、直したのですが、ひょっとすると、前のままかもしれません、、
グーグルクロム の場合は、
  右の画面に行くリンク
    ↑ を、クリック から、新しいタブで開く を、選択して、
  その、開いた、ページ で、🔃/再読み込み(左上)をクリックしてください。
これを、やってから、リンクしなおせば、修正後のページ になると思います。
ご迷惑おかけして、申し訳ありません。

右のコードですが、途中を省略してあって分かりにくいので、
これと同じポイントを押さえてコードを、書きました。
(ただし、ほとんど意味のないコードですが)

class Bob { val department: Department = Department() } class Department { val head: Head = Head() } class Head { val name: String? = "boss" } fun main() { val bob = Bob() print(bob?.department?.head?.name) } // boss
この場合は、途中に、null がないので、
結果、「boss」 まで、たどり着きました。

もし、途中で、null があった場合、NPE は、発生しませんが、
null を、返すはずです。

そこで、今のコード の、5行目 を、
   val head: Head? = null にして、
にして、実行してみましょう。
「安全呼び出し」 なので、最低、NPE は、投げません。

class Bob { val department: Department = Department() } class Department { val head: Head? = null } class Head { val name: String? = "boss" } fun main() { val bob = Bob() print(bob?.department?.head?.name) } // null
確かに、NPE は、投げずに、null になりました。
このように、連続して呼び出すことを、「チェーンさせる」 と言います。


 #9
let関数
右の、ページ と、持っている本を、総動員して、
多分使えるようにはなったと思う。
試しに、自分でコードを書いてみました。



いろいろコメントを書き込んでおきましたので、分かっていただけるかと、思います。
ただ、公式リファレンスのページに書いてあることが、さっぱり分からない。。
R って、reference?、block も、分からない、inlineも?。。
これは、私自身の宿題です。
とっ、ここで、気づいたのですが、
コンソールへの、出力の3つ目の、出力は、null です。
これは、謎です?、、これも私自身への宿題です。
一応、参考のために、こんなコードを書いてみました。



とにかく、頑張って謎を解こうと思っています。
申し訳ありませんが、let関数 に関しては、参考までという事にしておいてください。


 #10
まず、
  person?.department?.head = managersPool.getManager()
を、使うコードを書きました。

class Bob { val department: Department = Department() } class Department { val head: Head? = Head() } class Head { var name: String? = "boss" } class ManagerPool { val manager = "director" fun getManger(): String { return this.manager } } fun main() { val bob: Bob = Bob() val managerPool: ManagerPool = ManagerPool() bob?.department?.head?.name = managerPool.getManger() println(bob?.department?.head?.name) } // director
これは、null が、発生しない場合です。
しかし、null が、発生したら、決して、name に、上書きが出来なくなります。

class Bob { val department: Department = Department() } class Department { val head: Head? = null } class Head { var name: String? = "boss" } class ManagerPool { val manager = "director" fun getManger(): String { return this.manager } } fun main() { val bob: Bob = Bob() val managerPool: ManagerPool = ManagerPool() bob?.department?.head?.name = managerPool.getManger() println(bob?.department?.head?.name) } // null
null が、出力されました。


 #11 #12
エルビス演算子
エルビス演算子 とは、?: です。
  左・null許容型の値 ?: 右・null許容型の値
この時、
  左の値が null でなければ、左の値 を返し、
  null であったら、右の 非null許容型 の 値 を返します


 #11
では、if式 を、1行で、書いています。
 #12
では、エルビス演算子 を、使って、もっと短く書いています。

どちらも、null を、安全に扱っていると、言えます。

以下のコードは、コードを補って、IntelliJ で、実行してみました。




 #13
このコード は、よくわかりません。
出来るだけ早く理解したいと思っています。
そうしたら、書きます。


 #14
NPE-lover になった気持ちで、こんなコードにしました。




 #15
私は、キャスト は、継承関係 があれば、出来ることぐらいしか知りません。
ので、あまり深くは語れません。
参考までに、コードを書いてみました




 #16
これは、
  コレクション<T?>インスタンス.filterNotNull()
    / コレクション:List、Map、Set
    / T:型

右のコード を、まねて書きました。
これは、今まで学習したので、分かると思います。





まとめ
null とは、「無」 のことです。 ただし、「空文字列 / ""」 は、りっぱな、Stringインスタンス です。
・ NPE / NullPointerException とは、実は、null なのに、プロパティ/メソッド に、アクセスしたり、
 アクセス先 が、null だった時、発生する/投げられる 例外 です。
・ Kotlin のデータ型 には、通常の分類以外に、null許容型非null許容型
 で、分類する方法があります。
null許容型 にするには、? のようにします。
   / 例:String? Int? List<Double?>
・ Kotlin では、非null許容型 の変数に、null を、代入しようと知ると、コンパイルエラー になります。
・ Kotlin のコンパイラー は、コード1行を見て判断するのではなく、コードの文脈を見て、
 NPE が、起きるのかどうかを、判断します。
安全呼び出し をするには、「.」 ではなく、「?.」 を使って、アクセスします。
安全呼び出し をして、null であった時には、null を、返します
・ let関数:保留中
エルビス演算子 とは、?: です。
   左・null許容型の値 ?: 右・null許容型の値
 この時、
   左の値が null でなければ、左の値 を返し、
   null であったら、右の 非null許容型 の 値 を返します

!! は、「nullではないと(とりあえず)断言する演算子」 で、「どんな値」でも、「nullではないこと」 にします。
 そして、「その値」が、本当に null であるならば、 NPE を、投げます。
as? は、「安全なキャスト」 をします。
 もし、キャストできない時は、ClassCastException を投げる代わりに、null を、返します。
・ null許容型の、コレクション(List, Map, Set) があるとして、null でない要素のみを、抽出したい場合は、
 filterNotNull()メソッド を使えば、実現できます。