Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaからKotlinへの移行(相互運用ハマりポイント) #ca_kt

JavaからKotlinへの移行(相互運用ハマりポイント) #ca_kt

CA.kt #1 で発表したスライドです。
https://cyberagent.connpass.com/event/57963/

Taro Nagasawa

June 15, 2017
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

  1. JavaからKotlinへの移行 2017-06-15 CA.kt #1 長澤太郎 @ngsw_taro

  2. None
  3. 相互運用に関する ハマりポイント

  4. None
  5. None
  6. もくじ 1. Kotlinにはstaticという概念がない問題 2. FWのためだけにopen必要問題 3. デフォルトコンストラクタ問題 4. おまけ

  7. 自己紹介 • 長澤 太郎(たろーって呼んでね) • @ngsw_taro • エムスリー株式会社でエンジニアやってます • Kotlinエバンジェリスト(JetBrains黙認)

    • 日本Kotlinユーザグループ代表 • 「Kotlinスタートブック」の著者
  8. 1. Kotlinにはstaticと いう概念がない問題

  9. JUnit • Javaのテスティングフレームワーク • Kotlinからも使えます class CalculatorTest { private val

    sut = Calculator() @Test fun `1 + 2 = 3となること`() { val got = sut.plus(1, 2) assertThat(got).isEqualTo(3) } }
  10. Theoryテストでハマる! data class Fixture(val a: Int, val b: Int, val

    expected: Int) @RunWith(Theories::class) class CalculatorTest { private val sut = Calculator() @DataPoints fun getFixtures() = arrayOf(Fixture(1, 2, 3)) @Test fun `足し算結果を返すこと`(fixture: Fixture) { val got = sut.plus(fixture.a, fixture.b) assertThat(got).isEqualTo(fixture.expected) } } java.lang.Error: DataPoint field fixtures must be static
  11. Theoryテストでハマる! data class Fixture(val a: Int, val b: Int, val

    expected: Int) @RunWith(Theories::class) class CalculatorTest { private val sut = Calculator() @DataPoints fun getFixtures() = arrayOf(Fixture(1, 2, 3)) @Test fun `足し算結果を返すこと`(fixture: Fixture) { val got = sut.plus(fixture.a, fixture.b) assertThat(got).isEqualTo(fixture.expected) } } これをstaticにしたい java.lang.Error: DataPoint field fixtures must be static
  12. コンパニオンオブジェクト + @JvmStatic @RunWith(Theories::class) class CalculatorTest { companion object {

    @DataPoints @JvmStatic fun getFixtures() = arrayOf(Fixture(1, 2, 3) } JUnitが解釈してくれる!
  13. プロパティ + カスタムゲッターの場合 @RunWith(Theories::class) class CalculatorTest { companion object {

    @get:DataPoints @JvmStatic val fixtures: Array<Fixture> get() = arrayOf(Fixture(1, 2, 3) } getがミソ!
  14. プロパティ + バッキングフィールドの場合 @RunWith(Theories::class) class CalculatorTest { companion object {

    @DataPoints @JvmField val fixtures = arrayOf(Fixture(1, 2, 3) }
  15. AndroidのParcelableでも同じ問題が! data class Person(val name: String, val age: Int): Parcelable

    { companion object { @JvmField val CREATOR = object: Parcelable.Creator<Person> { override fun createFromParcel(p: Parcel): Person = Person(p.readString(), p.readInt()) } } ... } ←必要 KEEPにてデフォルトでのサポートが議論中 https://github.com/Kotlin/KEEP/pull/71
  16. 2. FWのためだけにopen 必要問題

  17. Kotlin + Spring Boot @SpringBootApplication class DemoApplication { @Bean fun

    runner() = CommandLineRunner { println("Hello") } } @Configuration class 'DemoApplication' may not be final. • Kotlinはデフォルトでfinal。継承許可はopenを明示 • Springは多くの場面でopenを要求する
  18. openを付けて解決! @SpringBootApplication open class DemoApplication { @Bean open fun runner()

    = CommandLineRunner { println("Hello") } }
  19. openを付けて解決! @SpringBootApplication open class DemoApplication { @Bean open fun runner()

    = CommandLineRunner { println("Hello") } } 面倒臭い!
  20. kotlin-springコンパイラプラグイン • アノテーションから判断して自動でopen指定してくれる • Gradleを使っているなら • Spring Initializrを使ってプロジェクト雛形を生成しているな ら、初期状態で設定済み apply

    plugin: 'kotlin-spring'
  21. Realmの場合 open class Product( var name: String? = null, var

    price: Double? = null ): RealmObject() 公式ドキュメント https://realm.io/docs/java/latest/ やはりopen必須...
  22. all-openコンパイラプラグイン buildscript { repositories { jcenter() } dependencies { classpath

    "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" } } apply plugin: 'kotlin-allopen' allOpen { annotation('io.realm.annotations.RealmClass') }
  23. all-openコンパイラプラグイン buildscript { repositories { jcenter() } dependencies { classpath

    "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" } } apply plugin: 'kotlin-allopen' allOpen { annotation('io.realm.annotations.RealmClass') } @RealmClassが付いたクラスをopenに
  24. さらばopen...! @RealmClass data class Product( var name: String? = null,

    var price: Double? = null ): RealmObject()
  25. さらばopen...! @RealmClass data class Product( var name: String? = null,

    var price: Double? = null ): RealmObject() 本来であればdataとopenは同居できないが、 all-openプラグインでこれが可能に(ヤバそう)
  26. 3. デフォルト コンストラクタ問題

  27. なにこれ? @RealmClass data class Product( var name: String? = null,

    var price: Double? = null ): RealmObject()
  28. なにこれ? @RealmClass data class Product( var name: String? = null,

    var price: Double? = null ): RealmObject() • Realmはデフォルトコンストラクタを要求する • でもnullにする予定ないし、そもそも面倒臭い
  29. 理想 @RealmClass data class Product( var name: String, var price:

    Double ): RealmObject()
  30. no-argコンパイラプラグイン buildscript { repositories { jcenter() } dependencies { classpath

    "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version" } } apply plugin: 'kotlin-noarg' noArg { annotation('io.realm.annotations.RealmClass') } @RealmClassが付いたクラスに デフォルトコンストラクタを自動生成
  31. 4. おまけ

  32. タイポしそう?定数が必要? val got = realm.where(Product::class.java) .equalTo("name", "Apple") .greaterThan("price", 0.4) .findFirst()

  33. リフレクションでプロパティ名を取る val got = realm.where(Product::class.java) .equalTo(Product::name.name, "Apple") .greaterThan(Product::price.name, 0.4) .findFirst()

  34. リフレクションでプロパティ名を取る val got = realm.where(Product::class.java) .equalTo(Product::name.name, "Apple") .greaterThan(Product::price.name, 0.4) .findFirst()

    ここが一致することを 保証できないのが怖くない?
  35. リフレクションでプロパティ名を取る val got = realm.where(Product::class.java) .equalTo(Product::name.name, "Apple") .greaterThan(Product::price.name, 0.4) .findFirst()

    「このプロパティならば、このデータ型の値が来る」 ということを保証できないのが怖くない?
  36. みんな大好き拡張関数 fun <T: RealmModel> RealmQuery<T>.equalTo( prop: KMutableProperty<T, String>, value: String

    ) = equalTo(property.name, value) fun <T: RealmModel> RealmQuery<T>.greaterThan( prop: KMutableProperty<T, Double>, value: Double ) = greaterThan(prop.name, value) その他、類似関数を多数定義
  37. ミスらない! val got = realm.where(Product::class.java) .equalTo(Product::name, "Apple") .greaterThan(Product::price, 0.4) .findFirst()

    • whereでProductを指定しているので、条件に使用でき るプロパティの持ち主もProductである必要がある • nameプロパティはStringなので、比較する値もString である必要がある
  38. まとめ • Kotlinにはstaticという概念がない ◦ @JvmStaticや@JvmFieldを上手く使う • FWのためだけに毎回openするのが面倒 ◦ Springならkotlin-springコンパイラプラグインを使う ◦

    all-openコンパイラプラグインを使う • デフォルトコンストラクタを提供したくない ◦ no-argコンパイラプラグインを使う