本記事の概要
社内プロジェクトでServer Side Kotlinを使ってコードを書くことになりました。 メンバーには自分含めJava民が多いです。Kotlinへの移行がちょっとでもスムーズになるように、Java民から見た「Kotlin特有の書き方だなぁ」ってところをまとめました。
目次
Kotlin慣れに良い本・サイト
[公式]イディオム集
https://kotlinlang.org/docs/java-to-kotlin-idioms-strings.html
[公式]コレクションタイプ
https://kotlinlang.org/docs/java-to-kotlin-collections-guide.html#operations-on-any-collection-type
リファレンス
https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/basic-types.html
(公式)SpringBootとH2DB使ってAPIつくる
https://kotlinlang.org/docs/jvm-spring-boot-restful.html#add-database-support
(Qiita)Kotlinの基本的な言語仕様を学習したまとめ
https://qiita.com/kerobot/items/ac8fa69955f9d3414f81
また、山根は以下の内容をこの本で勉強しました。 基本的な言語仕様から始まり、Kotlin + SpringBoot + H2/MyBatis + Vue.js使って簡単なアプリケーションを作るところまでがハンズオンで学べます。
変数定義周り
以下、山根がKotlinキャッチアップ時に"Javaと違うな..."って感じたところを記載していきます。
Null Safety
変数がデフォルトで非NULL型。これがデカい。
当たり前ですが、非NULL型の記事にnullを代入しようとすると、コンパイルの時点でエラーになります。 これにより皆さんの嫌いなNullPointerExceptionが起きづらくなります。
var a: String = "hogehoge" a = null //コンパイルエラー Null can not be a value of a non-null type String var b: Int = "hogehoge" b = null //コンパイルエラー Null can not be a value of a non-null type Int
型定義に"?"を付与することで、NULL許容型も作れます。
var a: String? = "hogehoge" a = null //コンパイルOK var b: Int? = "hogehoge" b = null //コンパイルOK
定義した変数を呼ぶとき
NULL許容で変数定義した場合は、下記の2つのパターンでメソッドを呼ぶ。 後者は非推奨なので実質セーフコール演算子がベターです。
セーフコール演算子
値がNULLでない場合のみメソッドが呼ばれる。
var a: String? = "hogehoge" println(a?.length) // 8 var b: String? = null println(b?.length) // null lengthメソッドは呼ばれない
非NULL表明演算子
一度NULL許容で定義したものを、NULL非許容な型に変更できる。
val nullable: String? = getNullable() val nonnull: String = nullable!!
またメソッド呼び出しと一緒に使うと、NULLだろうが何だろうがメソッドを呼ぶようになります。 当然nullとなっている変数に対してメソッドが呼ばれるとNullPointerExceptionが出ますので、注意して扱うようにしてください。
var a: String? = "hogehoge" println(a!!.length) // 8 var b: String? = null println(b!!.length) // ぬるぽ
変数定義時にval(再代入不可能) OR var(再代入可能)を必ず指定する
val
...再代入不可var
...再代入可。
val側はJavaでいうfinal定義みたいなもんです。 特に理由がない限りvalを使って、変数のスコープを小さく書くのがベターですね。
var a: String = "hogehoge" a = ”piyo” //OK val b: String= "hogehoge" b = ”piyo” //コンパイルエラー。 "Val cannot be reassined"(再代入すんな)
型推論可能
明示的に型を指定しなくても、推論してくれます。
val a = "hogehoge" a.startsWith("h") // startWithはStringクラスが持つメソッド。 コンパイルエラーにならない
lateinit var
lateinitはインスタンス生成時にまだ初期化したくない変数を定義するときに使います。
lateinit var hoge: String //初期化しなくてもコンパイルが通る
クラス定義周り
今までは変数を定義する時の話でしたが、以下はクラスを定義する際の話です。
CompanionObject
インスタンス生成せずにメソッド/変数を呼びたい時、static定義ではなく、 代わりにCompanionObjectなるものを用いる
class Hoge { //クラス内で定義 companion object { fun fuga(str : String) { println("$str") } } } Hoge.fuga("Kotlin")
変数定義についても同様 クラス内での変数定義の話
データクラス
class定義時にdata class
とすると、以下のメソッドを自動で実装してくれます。
equals
hashCode
copy
toString()
- Object(key1=value1, key2=value2)の文字列を返す
componentN
Javaで言うところのlombok.@Dataが言語のデフォルトになったイメージです。
import java.time.LocalDate data class Book { var id: Long? = null, var title: String? = null, var author: String? = null, )
val book = Book(1, "bocchan", "soseki") println(book.toString()) //Book(id=null, title="bocchan", author="soseki")
可視性
デフォルト/final
Kotlinでは、クラスはデフォルトで継承不可(Javaでいうfinalクラス状態)になっています。
明示的にコードで表現したい場合にはfinal
をつけることも可能です。
class User(val name: String, val age: Int) //継承不可 final class User(val name: String, val age: Int) //継承不可
open
他ファイルでもそのクラスを継承したい場合はopen
をつけます。
open class User(val name: String, val age: Int) //継承可
seald
同じファイルに定義されているクラスからのみ継承を許す (=別ファイルに定義されたクラスから継承不可)
seald class User(val name: String, val age: Int) //「別ファイルに定義されたクラスから」継承不可
getter/setter定義不要
class User1 { var name: String = "" }
var変数に対しては、getter/setterメソッドの定義が不要です。また、呼び出しも=
だけで代用できます。
val user = User1() user.name = "Tanaka" // user.setName()とか書かなくて良い println(user.name) //Tanaka
valで定義するとgetterのみ生成されます。(再代入不可なので当然っちゃ当然)
getter/setterを拡張することもできる
class User2 { lateinit var name: String // isValidNameプロパティに対するget()関数の処理を書き換える val isValidName: Boolean get() = name != "" }
上記のようにgetterメソッドを拡張することもできます。
val user = User2() user.name = "Tanaka" println(user.isValidName) //True
また、
class User3 { var name: String = "" //空文字の場合はデフォルト値として"Kotlin"を返す拡張 set(value) { if (value == "") { field = "Kotlin" } else { field = value } } }
上記のようにsetterメソッドを拡張することもできます。
val user = User3() user.name = "" //空文字を登録しているようで、拡張setterで実質"Kotlin"を代入している println(user.name) //"Kotlin"
コンストラクタ
プライマリコンストラクタ
class Person constructor( val firstName: String, val lastName: String, var isEmployed: Boolean = true )
コンストラクタに注釈・可視性修飾子がない場合は、constructor
を省略できる
class Person( val firstName: String, val lastName: String, var isEmployed: Boolean = true )
コンストラクタに注釈・可視性修飾子がある場合は、constructor
必須
class Person public consutructor ( val firstName: String, val lastName: String, var isEmployed: Boolean = true )
セカンダリコンストラクタ
class Person (private val name: String, private val age: Int) { // thisはプライマリコンストラクタを指す constructor(name: String) : this(name, 20) }
話が若干逸れるのですが、Kotlinのコンストラクタ周りについて調べている時、kotlinでSingletonパターン書こうとしたらどうなるんだろうな...と思ったりしました。 結果、objectで生成する方法が有力らしいです。
インスタンス生成周り
クラスを定義した後、クラスをインスタンス化して利用する時のお話です。
List,Mapのインスタンス生成(ミュータブル or 読み取り専用)
val mutableList = mutableListOf(1, 2, 3) //読み取り可能 + add可能 val List = listOf(1, 2, 3) //読み取り可能 addは不可...★ val mutableSet = mutableSetOf(1, 2, 3) //読み取り可能 + add可能 val set = setOf(1, 2, 3) //読み取り可能 addは不可...★
★なお、List,Setは"読み取り専用"なだけで、イミュータブルではない。 一度定義した後でも、下記のように参照先を書き換えると変更可能。
val mutableList = mutableListOf(1, 2, 3) val list: List<Int> = mutableList //Listインターフェース。 読み取り専用。この時点では(1,2,3) println(list) //1,2,3 mutableList.add(9) println(mutableList) //1,2,3,9 println(list) //1,2,3,9