Kotlin
特徴
拡張関数
既存のクラスに関数を追加できる。
fun String.hoge() {
println("hello")
}
"aaa".hoge() // "hello"
ラムダ引数の前でカッコを閉じる
最後の引数が関数の場合に限り、最後の引数の前でカッコを閉じられる。
fun hoge(str: String, func: () -> Unit){
// 任意の処理
}
hoge("aaa") { println("hoge") }
return
省略
ラムダ式内のhoge { "aaa" }
// hoge { return "aaa" } と同じ
レシーバーの指定
[レシーバーの型].() -> [戻り値]
class Person(name: String)
fun doSomething(func: Person.() -> Unit){
val person = Person("hoge")
person.func()
}
doSomeThing {
// ここに`Person`の匿名拡張関数を書くイメージ
// ここで`this`は`Person`なので、`$name`は`${this.name}`と同じ
println("my name is $name)
}
中置関数
infix
キーワードを使って関数を定義する。言葉っぽく書けるようになる。
1 add 2
のような書き方ができる。
中置関数の引数は1つである必要がある。
data class Point(x: Int, y: Int){
infix fun add(other: Point) = Point(x + other.x, y + other.y)
}
val p1 = Point(1, 1)
val p2 = Point(2, 2)
println(p1 add p2) // Point(3, 3)
with
的な関数も作れる。
// 第1引数で`T`型のインスタンスを受け取り、第2引数で指定した関数を適用する
fun <T> (receiver: T, block: T.() -> Unit): Unit
with("aaa") {
println(this)
println(this.toInt())
}
演算子オーバーロード
operator fun
で関数定義する。
data class Point(x: Int, y: Int)
operator fun Point.plus(that: Point) = Point(x + that.x, y + that.y)
val p1 = Point(1, 1)
val p2 = Point(2, 2)
println(p1 + p2) // Point(3, 3)
@DslMarker
アノテーション
ブロックをネストしたときにthis
が外側のthis
を指すようにしたいときに使う。
html {
head {
// ここで`this`が`this@html`になってしまうのを防ぎたいときに
}
}
reified
: 型パラメータでクラスオブジェクトを受け取る
クラスオブジェクトを受け取るとき、引数で受け取る代わりに型パラメータで受け取ることができる。
// 引数で渡すいつものパターン
val name = getClassName(Hoge::class)
// `reified`を使うとこう書ける
val name = getClassName<Hoge>()
関数はinline fun <reified T>
で定義する。
interface
など、override
することのできる関数には使用できない。
また、inline
なのでprivate
メンバを参照する関数にも使用できない。
class MyComponent {
val mapper: ObjectMapper = jacksonObjectMapper()
final inline fun <reified T> deserialize(value: String) = mapper.readValue<T>(value)
}
val component = MyComponent()
component.deserialize<String>("hoge")
reified
を書かない場合次のエラーが表示される。
Cannot use 'T' as reified type parameter. Use a class instead.
Classはデフォルトでfinal
継承するためにはopen
をつける。
open class Hoge
class ExtendedHoge: Hoge()
### `out`: "*型のサブクラス"を指定する
Javaでいう`<? extends Hoge>`。
```kotlin
// 戻り値に`Person`のサブクラス(Personはだめ)を指定
fun hoge(person: Mono<Person>): Mono<out Person>
関数をもらう関数
例。
fun <T> hoge(func: () -> T){}
apply
, let
, also
, run
の違い
Function | Target passed as | Returns |
---|---|---|
also | it | target |
apply | this | target |
let | it | block return value |
run | this | block return value |
文法
メソッド参照
他クラスのメソッドはHogeClass::function
関数や、自クラスのメソッドは::function
。
Kotlinらしい書き方
if
で条件によりオブジェクトに設定する場合はapply()
を使う
// bad
val hoge = if(isHoge) Fuga().enable() : Fuga()
// good
val hoge = Fuga().apply { if(isHoge) enable() }
拡張関数を使って引数を減らす
// bad
fun publishUser(user: User)
// good
fun User.publish()
nullの場合にデフォルト値を返す
// age == nullなら0
val age = user.age ?: 0
lambda
コンストラクタをメソッド参照で書く
JavaでいうHoge::new
。
Kotlinでは::Hoge
で書く。
パフォーマンス
Sequence
を使う
繰り返しですべての要素を処理する必要がないときは特に、大量の要素に対する処理があって最後でtake()
とかfirst()
とかしている場合は必須。
// List: 全件に対して`map()`の変換処理をする
(1..10).map { it * 10 }
.take(3)
// Sequence: `take(3)`があることにより先頭3件取得すると終了できるので、変換処理は3回のみ行う
(1..10).asSequence()
.map { it * 10 }
.take(3)
.toList()
Tips
分割代入
()
を使うと、オブジェクトから複数の変数を取り出して一度に定義できる。
data class Hoge(val a: String, val b: Int)
val (a, b) = Hoge("fuga", 1)
関数の戻り値がオブジェクトで、単に中の値を使いたいだけの場合などに便利。
val (result, status) = myFunc()
Ref:
(Destructuring Declarations)[https://kotlinlang.org/docs/reference/multi-declarations.html]
複数アノテーションを1つにまとめる
長いアノテーションを毎回書くのが面倒なときに。
@Hoge(name = "hoge")
@Fuga()
@Piyo()
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyAnno
ZonedDateTimeのZone変換
withZoneSameInstant()
を使う。
入力はJSTだけど処理の中でUTCに変換して使うときとか。
// JSTの日時に変換するフォーマッタ
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("Asia/Tokyo"))
// JSTのZonedDateTimeを生成
val jst = ZonedDateTime.parse("2000-10-10 00:00:00")
// UTCに変換
val utc = jst.withZoneSameInstant(ZoneOffset.UTC)
ZonedDateTimeから"YYYY/MM/ddTHH:mm:ssZ"(ISO-8601)の文字列を取得する
Instant.toString()
で取得できる。
ZonedDateTime
.now()
.toInstant()
.toString()
ISO-8601について:
https://www.coppermine.jp/docs/notepad/2016/12/iso-8601.html
LocalDateTimeをエポックミリ秒へ変換する
LocalDateTime.toEpochSecond()
を利用する。
引数にZoneOffset.UTC
を入れるとUTCになる。
ZonedDateTimeから変換する場合、ZoneOffsetがすでに入っているため引数なしでtoEpochSecond()
を呼べばいい。
val d = LocalDateTime.now()
// 秒で取得
val EpochSeconds: Long = d.toEpochSecond(ZoneOffset.UTC)
// ミリ秒で取得
val EpochMillis: Long = d.toInstant(ZoneOffset.UTC).toEpochMilli()
// エポックミリ秒からLocalDateTimeを生成
LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(longValue), ZoneOffset.UTC);
// エポックミリ秒からLocalDateを生成
LocalDate date = Instant.ofEpochMilli(longValue).atZone(ZoneOffset.UTC).toLocalDate();
Ref: How can I create a Java 8 LocalDate from a long Epoch time in Milliseconds?
ListをMapに変換
associateBy()
を使う。
第二引数を使ってキーの変換もできる。
val idUserMap = users.associateBy { it.id }
ref:
https://stackoverflow.com/questions/32935470/how-to-convert-list-to-map-in-kotlin
条件に一致するときに処理
takeIf()
を使うとif文を省略できる。
"hoge".takeIf { isEmpty() }.let { print(it) }
// nullチェックの代わりに書くと超便利
val hoge: String? = null
hoge?.takeIf { isEmpty() }.let {
doSomething(it)
}
Listを任意の件数ごとに分割して処理
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8)
.withIndex()
.groupBy { indexedValue -> indexedValue.index / 5 } // 5件ごと
.map { group -> group.value.map { println(it.value) } } // 分割リストごとの処理
例外が起きたらnullを返す関数
// Exceptionが起きたらnull、そうでなければ引数の戻り値をそのまま返す
fun <T> nullIfError(func: () -> T?): T? = try { func() } catch (e: Exception) { null }
Unit Test
引数で受け取った値をモック戻り値に利用する。
doAnswer()
を使う。
private val hogeRepo = mock<HogeRepository> {
on { save(any()) } doAnswer { (it.arguments[0] as Hoge) }
}
モックオブジェクトの呼び出し検証
argumentCaptor<>
を使う
val actual = argumentCaptor<Hoge>().apply { verify(hogeRepo, times(1)).save(capture()) }.firstValue
validation
Kotlinで@NonNull
を使うときは一工夫いる。
class User(
id: Int?
val name: String
) {
// nullを入れてからバリデーションが動くので、先にGetterを持つNullableのダミーのフィールドに入れてから、
// 遅延評価でフィールドにNonNullableにキャストして代入する
private val _id: Int? = id @NotNull get() = field
val id: Int by lazy { _id!! }
}
タイムアウト処理
coroutineを使うと簡単にできる。
build.gradleに依存関係を追加する。
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core"
with
Ref:
https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#timeout
ハマりどころ
Error
calls to static methods in Java interfaces are prohibited in JVM target 1.6. Recompile with '-jvm-target 1.8'
8
に設定
Settings > Build, Execution, Deployment > Compiler > Kotlin Compiler
Warning
Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable.
Javaのメソッドを呼んでいる箇所で、戻り値がnull許容かどうかわからないときに起こる。
関数の戻り値を明示的に指定してあげるとでなくなる。
← Javascript PowerShell →