Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

相互運用に関する ハマりポイント

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

自己紹介 ● 長澤 太郎(たろーって呼んでね) ● @ngsw_taro ● エムスリー株式会社でエンジニアやってます ● Kotlinエバンジェリスト(JetBrains黙認) ● 日本Kotlinユーザグループ代表 ● 「Kotlinスタートブック」の著者

Slide 8

Slide 8 text

1. Kotlinにはstaticと いう概念がない問題

Slide 9

Slide 9 text

JUnit ● Javaのテスティングフレームワーク ● Kotlinからも使えます class CalculatorTest { private val sut = Calculator() @Test fun `1 + 2 = 3となること`() { val got = sut.plus(1, 2) assertThat(got).isEqualTo(3) } }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

コンパニオンオブジェクト + @JvmStatic @RunWith(Theories::class) class CalculatorTest { companion object { @DataPoints @JvmStatic fun getFixtures() = arrayOf(Fixture(1, 2, 3) } JUnitが解釈してくれる!

Slide 13

Slide 13 text

プロパティ + カスタムゲッターの場合 @RunWith(Theories::class) class CalculatorTest { companion object { @get:DataPoints @JvmStatic val fixtures: Array get() = arrayOf(Fixture(1, 2, 3) } getがミソ!

Slide 14

Slide 14 text

プロパティ + バッキングフィールドの場合 @RunWith(Theories::class) class CalculatorTest { companion object { @DataPoints @JvmField val fixtures = arrayOf(Fixture(1, 2, 3) }

Slide 15

Slide 15 text

AndroidのParcelableでも同じ問題が! data class Person(val name: String, val age: Int): Parcelable { companion object { @JvmField val CREATOR = object: Parcelable.Creator { override fun createFromParcel(p: Parcel): Person = Person(p.readString(), p.readInt()) } } ... } ←必要 KEEPにてデフォルトでのサポートが議論中 https://github.com/Kotlin/KEEP/pull/71

Slide 16

Slide 16 text

2. FWのためだけにopen 必要問題

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

kotlin-springコンパイラプラグイン ● アノテーションから判断して自動でopen指定してくれる ● Gradleを使っているなら ● Spring Initializrを使ってプロジェクト雛形を生成しているな ら、初期状態で設定済み apply plugin: 'kotlin-spring'

Slide 21

Slide 21 text

Realmの場合 open class Product( var name: String? = null, var price: Double? = null ): RealmObject() 公式ドキュメント https://realm.io/docs/java/latest/ やはりopen必須...

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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に

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

さらばopen...! @RealmClass data class Product( var name: String? = null, var price: Double? = null ): RealmObject() 本来であればdataとopenは同居できないが、 all-openプラグインでこれが可能に(ヤバそう)

Slide 26

Slide 26 text

3. デフォルト コンストラクタ問題

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

理想 @RealmClass data class Product( var name: String, var price: Double ): RealmObject()

Slide 30

Slide 30 text

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が付いたクラスに デフォルトコンストラクタを自動生成

Slide 31

Slide 31 text

4. おまけ

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

リフレクションでプロパティ名を取る val got = realm.where(Product::class.java) .equalTo(Product::name.name, "Apple") .greaterThan(Product::price.name, 0.4) .findFirst() 「このプロパティならば、このデータ型の値が来る」 ということを保証できないのが怖くない?

Slide 36

Slide 36 text

みんな大好き拡張関数 fun RealmQuery.equalTo( prop: KMutableProperty, value: String ) = equalTo(property.name, value) fun RealmQuery.greaterThan( prop: KMutableProperty, value: Double ) = greaterThan(prop.name, value) その他、類似関数を多数定義

Slide 37

Slide 37 text

ミスらない! val got = realm.where(Product::class.java) .equalTo(Product::name, "Apple") .greaterThan(Product::price, 0.4) .findFirst() ● whereでProductを指定しているので、条件に使用でき るプロパティの持ち主もProductである必要がある ● nameプロパティはStringなので、比較する値もString である必要がある

Slide 38

Slide 38 text

まとめ ● Kotlinにはstaticという概念がない ○ @JvmStaticや@JvmFieldを上手く使う ● FWのためだけに毎回openするのが面倒 ○ Springならkotlin-springコンパイラプラグインを使う ○ all-openコンパイラプラグインを使う ● デフォルトコンストラクタを提供したくない ○ no-argコンパイラプラグインを使う