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

14c9795d267f5b85abb98ca5e8780646?s=47 Taro Nagasawa
November 27, 2015

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

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

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

November 27, 2015
Tweet

Transcript

  1. エバンジェリスト直伝! Kotlinを既存プロダクトで使う 2015-11-28 JJUC CCC 2015 Fall 長澤 太郎 (エムスリー株式会社)

  2. もくじ 1. Kotlinとは 2. 環境構築と導入 3. 既存プロダクトにちょい足し 4. がっつりKotlin化 5.

    便利なライブラリ 6. まとめ
  3. 自己紹介/会社紹介

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

    ◦ Android, Java, ときどきRuby, Scala ◦ SIerから転職、現在3年目 • コミュニティ活動 ◦ JJUG運営メンバー ◦ 日本Kotlinユーザグループ 会長 • 27歳、蟹座、 と が好き
  5. Kotlinとわたし • 夢中になり始めたのは2012年 (Kotlin歴は3年半以上) • 以来、ブログや勉強会で情報発信 • 商業誌、同人誌にも • Kotlinエバンジェリスト(自称)

  6. エムスリー株式会社

  7. エムスリー株式会社 インターネットを活用し健康で楽し く 長生きする人を1人でも増や し、不必要な医療コストを1円でも 減らす

  8. m3.com • 医療ポータルサイト • 医師会員数25万人+ (日本の80%+) • 医療ニュースや掲示板、 Stack Overflowや

    SlideShareのようなサービ スも • その他に医薬品情報、転職 支援、コンシューマ向け サービスなども
  9. m3.com Androidアプリ, iOSアプリ

  10. もくじ 1. Kotlinとは 2. 環境構築と導入 3. 既存プロダクトにちょい足し 4. がっつりKotlin化 5.

    便利なライブラリ 6. まとめ
  11. 1. Kotlinとは

  12. Kotlinとは • 2011年夏にJetBrainsより発表 • オープンソース (Apache License 2.0) • JVM言語、Android公式サポート、altJS

    • 静的型付けオブジェクト指向言語
  13. Better Java

  14. Java大好き♡ • 高性能・高信頼性のJava仮想マシン • 進化を続けるプログラミング言語 ◦ しかも後方互換性を維持 • 歴史長い、人口多い ◦

    ライブラリ・フレームワーク多い • 何より長く親しんで来た言語なので手になじむ、愛着がある
  15. とはいえ、

  16. Javaってもんにょりするよね。 そんなにトンガってない ちょうどいい言語が欲しい。

  17. もんにょり1: 冗長 MyFavoriteService<Fruit> s = new MyFavoriteService<>(banana);

  18. もんにょり1: 冗長 MyFavoriteService<Fruit> s = new MyFavoriteService<>(banana); =の右側からsの型は明らか <>演算子でだいぶ楽になった とは言え後方互換維持のため...

  19. Kotlinではこう val s = MyFavoriteService(banana)

  20. Kotlinではこう val s = MyFavoriteService(banana) 型推論でスッキリ!

  21. もんにょり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クラス
  22. Kotlinではこう data class Fruit( val name :String, val price :Int,

    val country :Country )
  23. Kotlinではこう data class Fruit( val name :String, val price :Int,

    val country :Country ) コンストラクタ引数が プロパティに! data修飾子で toStringなどを 自動生成
  24. もんにょり3: ぬるぽ String s = null; s.toUpperCase();

  25. もんにょり3: ぬるぽ String s = null; s.toUpperCase(); NullPointerException nullチェックすれば?

  26. nullなのか nullじゃないのか わからない

  27. Java的な工夫 @Nonnull String reverse(String s) {…} Optional<Character> initial(String s) {…}

  28. 誰にもnullは止められない!! @Nonnull Optional<Foo> findFoo() { return null; }

  29. Kotlinではこう val s1: String = null // NG val s2:

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

    String? = null // OK s2.toUpperCase() // NG if(s2 != null) { s2.toUpperCase() // OK } s2?.toUpperCase() // OK nullか否かを 明確に区別し、 扱いも厳しい
  31. Kotlin その他の特徴 • 型安全、配列専用の構文はない。クラス定義時に型パラメータに対 して変位指定ができる。 • 拡張関数: 静的に、既存の型にメソッドを生やせるようなやつ • 高階関数、関数オブジェクト、ラムダ式

    • 言語機能としての実装の委譲サポート • プロパティの委譲 • Javaとの相互運用性が非常に高い • Javaと同様のコンパイル速度 (目標)
  32. Kotlin Javaってもんにょりするよね。 そんなにトンガってない ちょうどいい言語が欲しい。

  33. まとめ • Java(JVM)が好きで、なくては生きていけない • とはいえJavaの文法とか機能とか... • Kotlinという選択肢 • JetBrains製 静的型付けオブジェクト指向言語

    • Null-Safetyなどのユニークな機能 • マイルド、現実見てる感
  34. 2. 環境構築と導入

  35. 環境構築 1. JDKを入れる (Kotlin的には1.6+)

  36. 環境構築 1. JDKを入れる (Kotlin的には1.6+) 2. IntelliJ IDEA 15を入れる (有償版・無償版どちらでも)

  37. 環境構築 1. JDKを入れる (Kotlin的には1.6+) 2. IntelliJ IDEA 15を入れる (有償版・無償版どちらでも) 以上

    ※必要に応じてプラグインのアップデートをすれば完璧
  38. 導入 • 前提 ◦ 既存Javaプロジェクト ◦ Gradle 1. おもむろにsrc/main/javaにHelloWorld.ktファイルを作る 2.

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

  40. Hello World package sample; class HelloWorld { companion object {

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

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

    }
  43. Hello World package sample fun main(args: Array<String>) { println("Hello, world")

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

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

    fun main(args: Array<String>): Unit { System.out.println("Hello, world"); } } } Javaとの親和性その1 Kotlinにはstaticは存在しない が、Javaからの見え方を意識して いる。
  46. まとめ • 環境構築は簡単 • 開発元がJetBrainsというだけありIDEAでのサポートが厚い • HelloWorld スッキリしたコードだが見慣れた雰囲気を感じる • アノテーションでJavaに歩み寄る

  47. 3. 既存プロダクトにちょい足し

  48. ちょい足しのススメ • JavaとKotlinの共存が簡単 • リスクが小さい • 例えばテストコードをKotlinで • 例えばデータクラスをKotlinで ◦

    特におすすめ ◦ ロジックがないのでKotlin初心者でも安心
  49. 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)"
  50. 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)" コンストラクタ インスタンス生成
  51. 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
  52. 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を オーバライド 式展開
  53. プロパティ 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)"
  54. プロパティ 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)" フィールドに似ているが、 内部状態は隠蔽されている
  55. コンストラクタ引数でプロパティ定義 class Id(val value: Long) { override fun toString(): String

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

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

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

    技術的な制約 ◦ JSONからJacksonを使ってデシリアライズする ◦ JSONの構造が微妙... ◦ Javaから使用することを意識する
  59. 疾患 data class Disease( val id: String, val name: String

    ): Serializable
  60. 論文 - プライマリコンストラクタ @JsonIgnoreProperties(ignoreUnknown = true) data class Thesis( val

    id: String, val title: String, @get:JvmName("isRead") val read: Boolean, val disease: Disease ): Serializable { ...
  61. 論文 - プライマリコンストラクタ @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用の名前指定
  62. 論文 - セカンダリコンストラクタ ... 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 ) ) ...
  63. 論文 - メソッド定義 ... fun withRead(read: Boolean) = copy(read =

    read) }
  64. 論文 - メソッド定義 ... fun withRead(read: Boolean) = copy(read =

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

    アノテーションでJavaに歩み寄る パート2 • 補助的なメソッドを提供してJavaフレンドリに
  66. 4. がっつりKotlin化

  67. Kotlinの機能を俯瞰 • dataクラス • プロパティ • Null-Safety • 高階関数、関数オブジェクト、ラムダ式 •

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

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

    if-else, for, when, try-catch 制御構文 • 拡張関数 • プロパティの委譲 (Delegated Property) 便利だが クセがある
  70. 拡張関数と Delegated Propertyを いかに使いこなすか Kotlinプログラミングのコツ ※一エバンジェリストの主観です

  71. 拡張関数 • 既存のクラスやインタフェースに変更を加えないで、 メソッドを追加したように見せる機能 fun String.hello() { println("Hello, $this!") }

    "world".hello()
  72. Java用ライブラリをいい感じに 例えばApache Commons Lang: // normal StringUtils.reverse("hoge") //=> egoh //

    extension function fun String.reverse() = StringUtils.reverse(this) "hoge".reverse() //=> egoh
  73. 特に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")
  74. 特に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を取る ようなメソッドが多い デフォルト引数
  75. 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);
  76. 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)
  77. 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>が 取得できる
  78. 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)
  79. Delegated Property • プロパティへのアクセスを別のオブジェクトに委譲する仕組み class Greeter { val message: String

    by lazy { "Hello, world" } } 標準ライブラリで提供さ れている関数 lazy
  80. ユーザ定義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) }
  81. 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(); } }
  82. 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) } } ※実用にはもう少し改良が必要
  83. まとめ • 拡張関数 ◦ 既存の型にメソッドを追加するイメージ • Delegated Property ◦ プロパティへのアクセスを他オブジェクトに委譲

    • 便利なので上手く使おう!乱用注意
  84. 5. 便利なライブラリ

  85. 便利なライブレリ・フレームワーク • KotterKnife • RxAndroid • Dagger2 • Realm

  86. 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 } }
  87. RxAndroid • RxJava + Android固有の機能 client.getTheses() .onErrorReturn { listOf() }

    .observeOn(AndroidSchedulers.mainThread()) .subscribe { theses -> thesesAdapter.theses = theses }
  88. 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() } }
  89. Realm • モバイルデータベース (とそのAPI群) • kaptで動く val t = Realm.getInstance(context).use

    { realm -> realm.where(Thesis::class.java) .equalTo("read", false) .findFirst() } Closeableの拡張関数 ローンパターン
  90. 6. まとめ

  91. • Javaはいい言語、JVMもいい環境 • 少なからずもんにょり感はある、特にAndroidでは

  92. • Javaはいい言語、JVMもいい環境 • 少なからずもんにょり感はある、特にAndroidでは • ちょうどいい言語が欲しい→それKotlin • 見たことがあるような文法 • Null-Safety、ラムダ式、型推論...便利そうだ!

  93. • Javaはいい言語、JVMもいい環境 • 少なからずもんにょり感はある、特にAndroidでは • ちょうどいい言語が欲しい→それKotlin • 見たことがあるような文法 • Null-Safety、ラムダ式、型推論...便利そうだ!

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

  95. at.m3.com/job 一緒にKotlinしましょう! Kotlin + Android Kotlin + Spring Boot etc...