Senの競技プログラミング備忘録

こけた問題を自分用の解説で載せる。けんちょんさんのブログを目指したい。質的にも量的にも。こけた問題だけに限定するけど

(競プロメイン)C++erから見たKotlin勉強記録-4 Null許容型変数、スマートキャスト、総称型、パッケージ

テスト環境

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などのように、"."を含ませても、最後のクラスを指定する"."以外は全部ちゃんとパッケージ名だと識別してくれる。

参考文献

やさしいKotlin入門 初版 野崎英一

dogwood008.github.io
公式リファレンス