(競プロメイン)C++erから見たKotlin勉強記録-4 Null許容型変数、スマートキャスト、総称型、パッケージ
はじめに
KotlinのNull許容型について。
(競プロメイン)C++erから見たKotlin勉強記録-1 型、基本制御文 - Senの競技プログラミング備忘録
(競プロメイン)C++erから見たKotlin勉強記録-2 Array, List, 関数 - Senの競技プログラミング備忘録
テスト環境
Try Kotlin
try.kotlinlang.org
paizaのKotlin環境
paiza.io
null許容型変数
Javaでの落ちる要因としては、何もないのを参照すること、NullPointerExceptionが非常に多い。Kotlinでは、コンパイル時で極力そのようなものにはコンパイルエラーをすることになっていて、ヒューマンエラー防止しやすい。
例として、Kotlinではnullを変数に代入するのはコンパイルエラーとなる。
var s1 : String = "Template" //s1 = null これはコンパイルエラー var s2 : String? = "Template2" s2 = null //型名の後に?をつけると、null許容するようになる。 println(s1); println(s2) println(s1.length); println(s2?.length) //s2のメンバ関数とか、要素を見るときにnull許容型なら変数名の後ろにもこのように?をつける。 //s2だけを渡す場合はつけない。
出力は
Template null 8 null
null許容型を受け取る関数
null許容型を受け取る、返す関数とかももちろん書ける。
下の関数では、String?型で受け取っているが、elseの中ではs.lengthと安全呼び出しを行っていない。
本来、しなければならないが、Kotlinのコンパイラがif-elseでnullチェックができてる、とセルフ判断してくれるので、そうでなくても大丈夫である。
fun printLength(s : String?){ if(s == null)println("null文字列です。") else println("${s}の長さは${s.length}です。") } fun main(args : Array<String>){ var s1 : String = "typedef" var s2 : String? = "long long ll" printLength(s1); printLength(s2) s2 = null printLength(s2) }
出力は
typedefの長さは7です。 long long llの長さは12です。 null文字列です。
!!演算子
null非許容型の、普通の変数にnull許容型変数を渡すと、コンパイルエラーになる。しかし、引数側を変えられないが(変えるのが一番だけど)便宜上中身を何とかして渡したい、というみなさんのための演算子。
null許容型の変数をnull非許容型に(無理やり)キャストできる。常用は怖いので最小限にとどめよう。
ちなみに、この演算子でキャストすると、その先でnullに対して表示とかをしたら、Kotlinで唯一のNullPointerExceptionを吐く。
fun printStr(s : String) = println(s + "!!!") fun main(args : Array<String>){ var s1 : String = "Kamehameha" var s2 : String? = "Final Flash" printStr(s1) printStr(s2!!)//キャストすると、渡せる。 s2 = null printStr(s2!!)//無理やりキャストしてるため、 //ここではコンパイルエラーにはならず、実行時NullPointerExceptionになる }
出力
Kamehameha!!! Final Flash!!! Exception in thread "main" kotlin.KotlinNullPointerException at Simplest_versionKt.main(Simplest version.kt:9)
?:演算子
エルピス演算子とも。null判定限定のC++の三項演算子のようなもの。null判定のif-elseを書くことでコードが汚くなるor書くのが面倒な方向け。
書式としては「num?.toString() ?: "no data"」のように書く。?:の前半がnullではなかったら前半(例ではnum.toString())を返し、nullなら?:の後半(例では”no data”)を返す。
fun getLength(s : String?) : Int{ if(s == null)return 0 else return s.length } //このように簡潔に書けちゃう! fun printLengthUsingElpisOperator(s : String) : Int{ return s?.length ?: 0 }
スマートキャスト
すべての型は、KotlinではAnyクラスを継承して実装されているとclassのところで言及した。
なら、Any型の引数を関数に設定すればなんでも受け取れるのでは???
できます。
fun printInfo(obj : Any) = println(obj.toString()) open class Human(var name : String = "Sen") class Student(var studentID : Int = 21616676) : Human() fun main(args : Array<String>){ var num = 114514 var str : String = "ichiro" var obj1 = Human() var obj2 = Student() printInfo(num) printInfo(str) printInfo(obj1) printInfo(obj2) }
出力
114514 ichiro Human@28a418fc Student@5305068a
クラスのtoString()はoverrideしないとクラス名と内部的識別番号しか返さないですね。
さて、Any型だと、基本的にAnyに含まれているあの3つのメソッドしか使えない(明確に型が分かったとしても、Anyのメソッドしか使えない)・・・かと思われていたが
なんとKotlinでは、コード内のifなどで型を明確に判定できたのならば、勝手にAny型を判定した型にキャストして、その型として扱ってくれるのである!
とても賢E、smartなのでスマートキャストと呼ばれる機能。
fun do_something (obj : Any){ if(obj is Int){ println("Int型だね。+3しておくわ result->${obj + 3}") } else if(obj is String){ println("String型だね。+3を後ろにくっつけておくわ result->${obj + "+3"}") } else { println("想定外の型だからなにもせんわ") } } fun main(args : Array<String>){ var num = 114514 var str : String = "ichiro" var d : Double = 1.14514 do_something(num); do_something(str); do_something(d) }
総称型(ジェネリクス)
C++におけるテンプレートに相当。
template<class T> T ADD(T a, T b){return a + b;} template<class T> class Box{ public: string rabel; T content; void printval(){cout << "rabel == " << rabel << " and content is " << content << endl;} };
Kotlinでは、これに似たような書き方でできる。そのTを変更するのももちろんOK。
class Box<T>(var rabel : String, var content : T){ fun printval() = println("rabel == ${rabel} and content is ${content}.") }
関数の場合などは以下の記事を参照
Java開発者に送る、Kotlinのジェネリック - Qiita
パッケージ
Javaと同じように、Kotlinにはパッケージという概念がある。それぞれのファイルにパッケージといって名前をつけ、異なるパッケージ内での名前の衝突を防ぐという意味を持つ。C++でいうとnamespace。
基本的に、www.example.comがリリースしたソースコードのパッケージ名は「com.example」というのが世界的な規範。
同じクラス
package appMainFrame //このように定義できる。 import appMainFrame.Main //appMainFrameパッケージのクラスMainについて、毎回appMainFrame.Mainと書かなくて済むようにする。 //複数のクラスがあったときに、import appMainFrame.* のようにすると、全部importできる。
ちなみに、パッケージ名がcom.example.geometry2dなどのように、"."を含ませても、最後のクラスを指定する"."以外は全部ちゃんとパッケージ名だと識別してくれる。