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

エバンジェリスト直伝!Kotlinを既存プロダクトで使う #jjug_ccc #jjug #jkug

Taro Nagasawa
November 27, 2015

エバンジェリスト直伝!Kotlinを既存プロダクトで使う #jjug_ccc #jjug #jkug

JJUG CCC 2015 Fallで発表したスライドです。
by 自称Kotlinエバンジェリスト

Taro Nagasawa

November 27, 2015
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

  1. 自己紹介 • 長澤 太郎 「たろー」 って呼んでください • ソフトウェアエンジニア ◦ 主にAndroidアプリ開発担当

    ◦ Android, Java, ときどきRuby, Scala ◦ SIerから転職、現在3年目 • コミュニティ活動 ◦ JJUG運営メンバー ◦ 日本Kotlinユーザグループ 会長 • 27歳、蟹座、 と が好き
  2. m3.com • 医療ポータルサイト • 医師会員数25万人+ (日本の80%+) • 医療ニュースや掲示板、 Stack Overflowや

    SlideShareのようなサービ スも • その他に医薬品情報、転職 支援、コンシューマ向け サービスなども
  3. Java大好き♡ • 高性能・高信頼性のJava仮想マシン • 進化を続けるプログラミング言語 ◦ しかも後方互換性を維持 • 歴史長い、人口多い ◦

    ライブラリ・フレームワーク多い • 何より長く親しんで来た言語なので手になじむ、愛着がある
  4. もんにょり2: ボイラープレート(定型コード) public final class Fruit { private final String

    name; private final int price; private final Country country; public Fruit(final String name, final int price, final Country country) { this.name = name; this.price = price; this.country = country; } public String getName() { return name; } public int getPrice() { return price; } public Country getCountry() { return country; } public Fruit withName(final String name) { return new Fruit(name, price, country); } public Fruit withPrice(final int price) { return new Fruit(name, price, country); } public Fruit withCountry(final Country country) { return new Fruit(name, price, country); } // toStringとか // equalsとか // hashCodeとか public static Builder builder() { return new Builder(); } public static class Builder { private String name; private int price; private Country country; public Fruit build() { return new Fruit(name, price, country); } public Builder name(final String name) { this.name = name; return this; } public Builder price(final int price) { this.price = price; return this; } public Builder country(final Country country) { this.country = country; return this; } • データを表現するクラス • イミュータブルにしたい • private final フィールド • コンストラクタ • getter • toString, equals, hashCode • Builderクラス (Lombokライクな) Fruitクラス
  5. Kotlinではこう data class Fruit( val name :String, val price :Int,

    val country :Country ) コンストラクタ引数が プロパティに! data修飾子で toStringなどを 自動生成
  6. Kotlinではこう val s1: String = null // NG val s2:

    String? = null // OK s2.toUpperCase() // NG if(s2 != null) { s2.toUpperCase() // OK } s2?.toUpperCase() // OK
  7. Kotlinではこう val s1: String = null // NG val s2:

    String? = null // OK s2.toUpperCase() // NG if(s2 != null) { s2.toUpperCase() // OK } s2?.toUpperCase() // OK nullか否かを 明確に区別し、 扱いも厳しい
  8. 導入 • 前提 ◦ 既存Javaプロジェクト ◦ Gradle 1. おもむろにsrc/main/javaにHelloWorld.ktファイルを作る 2.

    「as Kotlin(Gradle) module」をクリックする 3. 流れに身をまかせる 4. build.gradleにKotlinの設定が自動的に追記される ※Android Studioもだいたい同じ ※src/main/kotlinディレクトリ作るといいよ
  9. Hello World package sample; class HelloWorld { companion object {

    @JvmStatic fun main(args: Array<String>): Unit { System.out.println("Hello, world"); } } }
  10. Hello World package sample; class HelloWorld { companion object {

    @JvmStatic fun main(args: Array<String>): Unit { System.out.println("Hello, world"); } } } 普通こんな コード書きません
  11. Hello World package sample fun main(args: Array<String>) { println("Hello, world")

    } パッケージ直下に関数 println Kotlin標準API 変数の型は後置 配列は ジェネリクス Unitのときは 省略可 セミコロン 不要 funで 関数定義
  12. ちなみに... package sample; class HelloWorld { companion object { @JvmStatic

    fun main(args: Array<String>): Unit { System.out.println("Hello, world"); } } }
  13. ちなみに... package sample; class HelloWorld { companion object { @JvmStatic

    fun main(args: Array<String>): Unit { System.out.println("Hello, world"); } } } Javaとの親和性その1 Kotlinにはstaticは存在しない が、Javaからの見え方を意識して いる。
  14. Kotlinのクラス class Id(value: Long) { private val _value: Long =

    value fun getValue(): Long = _value override fun toString(): String = "Id($_value)" } val id = Id(123) id.getValue() //=> 123 id.toString() //=> "Id(123)"
  15. Kotlinのクラス class Id(value: Long) { private val _value: Long =

    value fun getValue(): Long = _value override fun toString(): String = "Id($_value)" } val id = Id(123) id.getValue() //=> 123 id.toString() //=> "Id(123)" コンストラクタ インスタンス生成
  16. Kotlinのクラス class Id(value: Long) { private val _value: Long =

    value fun getValue(): Long = _value override fun toString(): String = "Id($_value)" } val id = Id(123) id.getValue() //=> 123 id.toString() //=> "Id(123)" 値を保持し、 getterを提供 メソッド呼び出し デフォルトは public
  17. Kotlinのクラス class Id(value: Long) { private val _value: Long =

    value fun getValue(): Long = _value override fun toString(): String = "Id($_value)" } val id = Id(123) id.getValue() //=> 123 id.toString() //=> "Id(123)" toStringを オーバライド 式展開
  18. プロパティ class Id(value: Long) { val value: Long = value

    override fun toString(): String = "Id($value)" } val id = Id(123) id.value //=> 123 id.toString() //=> "Id(123)"
  19. プロパティ class Id(value: Long) { val value: Long = value

    override fun toString(): String = "Id($value)" } val id = Id(123) id.value //=> 123 id.toString() //=> "Id(123)" フィールドに似ているが、 内部状態は隠蔽されている
  20. コンストラクタ引数でプロパティ定義 class Id(val value: Long) { override fun toString(): String

    = "Id($value)" } val id = Id(123) id.value //=> 123 id.toString() //=> "Id(123)"
  21. データクラス data class Id(val value: Long) val id = Id(123)

    id.value //=> 123 id.toString() //=> "Id(value=123)"
  22. データクラス data class Id(val value: Long) val id = Id(123)

    id.value //=> 123 id.toString() //=> "Id(value=123)" data修飾子 本体ブロックなし (optional) toStringなどの実装が 提供される
  23. m3.comアプリでやってみる • 対象データ ◦ 「臨床ダイジェスト」における「論文 (Thesis)」 ◦ 論文に紐付いた疾患 (Disease) •

    技術的な制約 ◦ JSONからJacksonを使ってデシリアライズする ◦ JSONの構造が微妙... ◦ Javaから使用することを意識する
  24. 論文 - プライマリコンストラクタ @JsonIgnoreProperties(ignoreUnknown = true) data class Thesis( val

    id: String, val title: String, @get:JvmName("isRead") val read: Boolean, val disease: Disease ): Serializable { ...
  25. 論文 - プライマリコンストラクタ @JsonIgnoreProperties(ignoreUnknown = true) data class Thesis( val

    id: String, val title: String, @get:JvmName("isRead") val read: Boolean, val disease: Disease ): Serializable { ... そのままだと JavaからはgetRead() get: ゲッターにアノテーション付加 JvmNameアノテーション Java用の名前指定
  26. 論文 - セカンダリコンストラクタ ... constructor( @JsonProperty("id") id: String, @JsonProperty("title") title:

    String, @JsonProperty("read") read: Boolean, @JsonProperty("diseaseId") diseaseId: String @JsonProperty("diseaseName") diseaseName: String ): this( id = id, title = title, read = read, disease = Disease( id = diseaseId, name = diseaseName ) ) ...
  27. 論文 - メソッド定義 ... fun withRead(read: Boolean) = copy(read =

    read) } dataアノテーションに より自動生成される copyメソッド 名前付き引数 Java用にLombokライクな メソッドを用意
  28. まとめ • リスクヘッジのため、まずはちょい足し • データクラスがおすすめ • プロパティ便利 • dataアノテーション便利 •

    アノテーションでJavaに歩み寄る パート2 • 補助的なメソッドを提供してJavaフレンドリに
  29. Kotlinの機能を俯瞰 • dataクラス • プロパティ • Null-Safety • 高階関数、関数オブジェクト、ラムダ式 •

    if-else, for, when, try-catch 制御構文 • 拡張関数 • プロパティの委譲 (Delegated Property)
  30. Kotlinの機能を俯瞰 • dataクラス • プロパティ • Null-Safety • 高階関数、関数オブジェクト、ラムダ式 •

    if-else, for, when, try-catch 制御構文 • 拡張関数 • プロパティの委譲 (Delegated Property) まあ普通
  31. Kotlinの機能を俯瞰 • dataクラス • プロパティ • Null-Safety • 高階関数、関数オブジェクト、ラムダ式 •

    if-else, for, when, try-catch 制御構文 • 拡張関数 • プロパティの委譲 (Delegated Property) 便利だが クセがある
  32. Java用ライブラリをいい感じに 例えばApache Commons Lang: // normal StringUtils.reverse("hoge") //=> egoh //

    extension function fun String.reverse() = StringUtils.reverse(this) "hoge".reverse() //=> egoh
  33. 特にAndroidでは真価を発揮 // this: Activity or Context etc Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show()

    fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, message, duration).show() } toast("Hello")
  34. 特にAndroidでは真価を発揮 // this: Activity or Context etc Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show()

    fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, message, duration).show() } toast("Hello") 引数にContextを取る ようなメソッドが多い デフォルト引数
  35. m3.comアプリでやってみる • Jacksonを用いたデシリアライズ // Java public enum ObjectMapperUtils { public

    static <T> Optional<T> read(ObjectMapper objectMapper, String json, Class<T> clazz) { try { return Optional.of(objectMapper.readValue(json, clazz)); } catch(Exception e) { return Optional.absent(); } } } ~~~~~~~~ Optional<Thesis> thesis = ObjectMapperUtils.read(objectMapper, json, Thesis.class);
  36. ObjectMapperの拡張関数を定義 inline fun <reified T: Any> ObjectMapper.read(json: String): T? =

    try { readValue(json, T::class.java) } catch(e: Exception) { null } val thesis: Thesis? = objectMapper.read(json) // val thesis = objectMapper.read<Thesis>(json)
  37. ObjectMapperの拡張関数を定義 inline fun <reified T: Any> ObjectMapper.read(json: String): T? =

    try { readValue(json, T::class.java) } catch(e: Exception) { null } val thesis: Thesis? = objectMapper.read(json) // val thesis = objectMapper.read<Thesis>(json) reified: 型パラ メータの具体化 AnyでTの上限指定。 デフォはAny? reifiedの場合は inline化必須 Class<T>が 取得できる
  38. ObjectMapperの拡張関数を定義 inline fun <reified T: Any> ObjectMapper.read(json: String): T? =

    try { readValue(json, T::class.java) } catch(e: Exception) { null } val thesis: Thesis? = objectMapper.read(json) // val thesis = objectMapper.read<Thesis>(json)
  39. ユーザ定義Delegated Property class MyProp<T>(initial: T) { private var value: T

    = initial operator fun getValue(thisRef: Any, prop: KProperty<*>): T { return value } operator fun setValue(thisRef: Any, prop: KProperty<*>, value: T) { this.value = value } } class User(name: String) { var name: String by MyProp(name) }
  40. m3.comアプリでやってみる • インテントから論文を受け取る // Java public class ThesisActivity extends Activity

    { Optional<Thesis> thesis = Optional.absent(); protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState) Serializable t = getIntent() .getExtras() .getSerialize("thesis"); thesis = (t != null) ? Optional.of((Thesis) t) : Optional.<Thesis>absent(); } }
  41. Delegated Propertyでスッキリ fun <T: Serializable> Activity.extra() = object: ReadOnlyProperty<Activity, T?>

    { override fun getValue(thisRef: Activity, prop: KProperty<*>): T? = intent.extras.getSerializable(prop.name) as? T } class ThesisActivity: Activity() { val thesis: Thesis? by extra() override fun onCreate(savedInstanceState: Bundle?) { Log.d("TEST", thesis?.title) } } ※実用にはもう少し改良が必要
  42. KotterKnife • View bindingライブラリ • Butter KnifeのKotlin版 class ThesisView(context: Context):

    FrameLayout(context) { val titleView: TextView by bindView(R.id.title) fun set(thesis: Thesis) { titleView.text = thesis.title } }
  43. RxAndroid • RxJava + Android固有の機能 client.getTheses() .onErrorReturn { listOf() }

    .observeOn(AndroidSchedulers.mainThread()) .subscribe { theses -> thesesAdapter.theses = theses }
  44. Dagger2 • DIフレームワーク、Annotation Processingで • kaptで動く: Kotlin Annotation Processing Tool

    • lateinit修飾子で初期化の遅延を明示しNullable対策 class ThesisListActivity { @Inject lateinit var client: Client fun onCreate(savedInstanceState: Bundle?) { // (省略) M3ComApp.component(this).inject(this) client.getTheses() } }
  45. Realm • モバイルデータベース (とそのAPI群) • kaptで動く val t = Realm.getInstance(context).use

    { realm -> realm.where(Thesis::class.java) .equalTo("read", false) .findFirst() } Closeableの拡張関数 ローンパターン
  46. • Javaはいい言語、JVMもいい環境 • 少なからずもんにょり感はある、特にAndroidでは • ちょうどいい言語が欲しい→それKotlin • 見たことがあるような文法 • Null-Safety、ラムダ式、型推論...便利そうだ!

    • まずはデータクラスでちょい足し • 拡張関数とDelegated Propertyを駆使してがっつりKotlin • Java向けKotlin向け問わず便利なライブラリ・FWを使う