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()メソッド を使えば、実現できます。