経緯
社内PJでKotlinを使ってコードを書くことになりました。
メンバーには自分含めJava民が多いです。Kotlinへの移行がちょっとでもスムーズになるように、Java民から見た「Kotlin特有の書き方だなぁ」ってところをまとめました。
å
Kotlin慣れに良いサイト(たぶん)
(公式)イディオム
(公式)コレクションタイプ
Javaでこう書いてたものがKotlinだとこう、みたいな感じ
リファレンス
コレも公式。
公式を個人が日本語訳したもの。
右上の「TRY ONLINE」からオンライン環境でコーディングできる
(公式)SpringBootとH2DB使ってAPIつくる
英語が面倒だけど実処理書こうとするとこんな感じ
Kotlinの基本的な言語仕様を学習したまとめ
ぶっちゃけ本記事の上位互換。
変数定義周り
Null Safety
変数がデフォルトで非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表明演算子(きっと非推奨)
指摘頂いて追記。
NullableなものをnonNullに変更できる。
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) // ぬるぽ
変数定義時にvar(再代入可能) OR 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とかあった気がするけどなんだっけ。。。
lateinitはインスタンス生成時にまだ初期化したくないフィールドに対して使うもので、型推論とは別物。
クラス定義周り
クラスを作る側の話。
CompanionObject
インスタンス生成せずにメソッド/変数を呼びたい時、static定義ではなく、
代わりにCompanionObjectなるものを用いる
class Hoge {
//クラス内で定義
companion object {
fun fuga(str : String) {
println("$str")
}
}
}
Hoge.fuga("Kotlin")
変数定義についても同様
クラス内での変数定義の話
データクラス
クラスにlombok(@Data)が外部ライブラリなしで実現できるようになったイメージ
エンティティクラスとかによく使われそう
equals
hashCode
copy
toString()
Object(key1=value1, key2=value2)型のもの
componentN
import java.time.LocalDate
data class Book {
var id: Long? = null,
var title: String? = null,
var author: String? = null,
var releaseDate: LocalDate? = null
)
デフォルトで継承不可(Javaでいうfinalクラスみたいなもん)
class User(val name: String, val age: Int) //継承不可
明治的に継承させたくない場合はseald
をつける
指摘頂いて修正。sealdの挙動は下記でした。
- 同じファイルに定義されているクラスからのみ継承を許す (=別ファイルに定義されたクラスから継承不可)
seald class User(val name: String, val age: Int) //「別ファイルに定義されたクラスから」継承不可
明示的に継承させたくない場合はfinal
をつける(Javaと一緒だが、デフォルトで継承不可なのであんまり使うことなさそう)
final class User(val name: String, val age: Int) //継承不可
他ファイルでもそのクラスを継承したい場合はopen
をつける
open class User(val name: String, val age: Int) //継承可
getter/setter定義不要
class User1 {
var name: String = ""
}
val user = User1()
user.name = "Tanaka" // user.setName()とかしなくて良い
println(user.name) //Tanaka
・プロパティをvalで定義するとgetterのみ生成される(再代入不可なので当然っちゃ当然)
getter/setterを拡張することもできる
getter拡張
class User2 {
lateinit var name: String
// isValidNameプロパティに対するget()関数の処理を書き換える
val isValidName: Boolean
get() = name != ""
}
val user = User2()
user.name = "Tanaka"
println(user.isValidName) //True
setter拡張
class User3 {
var name: String = ""
//空文字の場合はデフォルト値として"Kotlin"を返す拡張
set(value) {
if (value == "") {
field = "Kotlin"
} else {
field = value
}
}
}
val user = User3()
user.name = "" //空文字を登録しているようで、拡張setterで実質"Kotlin"を代入している
println(user.name) //"Kotlin"
プライマリコンストラクタ、セカンダリコンストラクタ
プライマリコンストラクタ
class Person constructor(
val firstName: String,
val lastName: String,
var isEmployed: Boolean = true
)
コンストラクタに注釈・可視性修飾子がない場合は、constroctor
を省略できる
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でSingletonパターン書こうとしたらどうなるんだろうな...と思った。
結果、objectで生成する方法が有力らしい
インスタンス生成周り
クラスを使う側の話。
List,Mapのインスタンス生成(ミュータブル or 読み取り専用OK)
val mutableList = mutableListOf(1, 2, 3) //可変 add可能
val List = listOf(1, 2, 3) //読み取り専用 getのみ可能...★
val mutableSet = mutableSetOf(1, 2, 3) //可変 add可能
val set = setOf(1, 2, 3) //読み取り専用 getのみ可能...★
★なお、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