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

PragmaticなJVM言語のルーキー「Kotlinってなんだ?」 #biz_kt

PragmaticなJVM言語のルーキー「Kotlinってなんだ?」 #biz_kt

ビズリーチ社内勉強会で発表したスライドです。

Taro Nagasawa

April 08, 2016
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

  1. • 長澤 太郎 たろーって呼んでね • @ngsw_taro • プログラマー@エムスリー株式会社 ◦ Android,

    Kotlin, Java, Scala, Rubyなど • Kotlinエバンジェリスト(JetBrains黙認) • やすべえとディズニーが好き 自己紹介
  2. エバンジェリストな私 • Kotlin歴 4年 • 日本Kotlinユーザグループ代表 • 講演実績多数 ◦ DroidKaigi

    2015, 2016 ◦ JJUG CCC 2015 Fall ◦ Kotlin勉強会@Sansan (2回) ◦ 関西Kotlin勉強会 (3回) • 執筆実績多数 ◦ 商業誌 x 3, 同人誌 x 1 ◦ Kotlin本 目下執筆中!
  3. 冗長 MyFavoriteService s = new MyFavoriteService(); BiFunction<A, B, C> f

    = (a, b) -> {...}; ImmutableList<Integer> ints = immutableListOf(1); ImmutableList<? extends Number> nums = ints;
  4. 冗長 MyFavoriteService s = new MyFavoriteService(); BiFunction<A, B, C> f

    = (a, b) -> {...}; ImmutableList<Integer> ints = immutableListOf(1); ImmutableList<? extends Number> nums = ints; 当たり前だよね。。
  5. 冗長 MyFavoriteService s = new MyFavoriteService(); BiFunction<A, B, C> f

    = (a, b) -> {...}; ImmutableList<Integer> ints = immutableListOf(1); ImmutableList<? extends Number> nums = ints; ラムダ式便利だなー...?ちょっと軸は違うけど
  6. MyFavoriteService s = new MyFavoriteService(); BiFunction<A, B, C> f =

    (a, b) -> {...}; ImmutableList<Integer> ints = immutableListOf(1); ImmutableList<? extends Number> nums = ints; 冗長 常に共変でもよさそう!
  7. ボイラープレート class User { private final Long id; private final

    String name; public User(final Long id, final String name) { this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final User user = (User) o; if (!id.equals(user.id)) return false; return name.equals(user.name); } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + name.hashCode(); return result; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } } • 例えば Userクラス ◦ idとnameを持つ • ゲッター • equals, hashCode, toString
  8. ボイラープレート class User { private final Long id; private final

    String name; public User(final Long id, final String name) { this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final User user = (User) o; if (!id.equals(user.id)) return false; return name.equals(user.name); } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + name.hashCode(); return result; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } } • 例えば Userクラス ◦ idとnameを持つ • ゲッター • equals, hashCode, toString • idのデフォルト値の使用を許可 するためにコンストラクタを追加 • Lombokのようなビルダーパ ターンを実装
  9. ボイラープレート class User { private final Long id; private final

    String name; public User(final Long id, final String name) { this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final User user = (User) o; if (!id.equals(user.id)) return false; return name.equals(user.name); } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + name.hashCode(); return result; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } } • 例えば Userクラス ◦ idとnameを持つ • ゲッター • equals, hashCode, toString • idのデフォルト値の使用を許可 するためにコンストラクタを追加 • Lombokのようなビルダーパ ターンを実装 関心: 読み取り専用 id と name が存在する
  10. それ本当? String s = reverse("Hello"); String got = (s !=

    null) ? s.toUpperCase() : null; nullは返し得ないメソッド
  11. それ本当? 実際、本当 String s = reverse("Hello"); String got = (s

    != null) ? s.toUpperCase() : null; nullは返し得ないメソッド
  12. それ本当? 実際、本当 String s = reverse("Hello"); String got = (s

    != null) ? s.toUpperCase() : null; でも、そんなことしたら 余計に冗長になるだけ nullは返し得ないメソッド
  13. 後方互換: 型安全性に問題あり Integer[] ints = { 0 }; Object[] objs

    = ints; objs[0] = "Hello"; コンパイルは通るがクラッシュする Effective Java 項目25 「配列よりリストを選ぶ」
  14. つらみまとめ • 冗長、ボイラープレートの嵐 • nullポインタのデリファレンス問題 • 後方互換性の維持 ◦ 古い文法が存在する ◦

    型安全性に問題あり とはいえ 良いところもたくさんあるので 今後も選択肢として有力な言語だ と思ってます。
  15. Kotlinを選ぶ理由 • Javaよりも安全、簡潔なコードを書ける • 実用的な表現力を維持しつつ、Scalaよりも簡潔 ◦ JetBrains曰く... • Javaエンジニアにとって、学習コストが低そう ◦

    精神的な敷居もやや低めっぽい • JetBrainsの後ろ盾がある • 公式によるAndroidのサポート ◦ かなり真剣に取り組んでいる模様
  16. Hello World fun main(args: Array<String>) { println("Hello, world!") } 関数定義のキーワード

    エントリポイント 型は後置 配列が ジェネリクス セミコロン 不要 パッケージ直下に 関数を定義できる
  17. 配列は普通のクラス val ints: Array<Int> = arrayOf<Int>(0) val anys: Array<Any> =

    ints anys[0] = "Hello" 変数定義の キーワード 変数名 型
  18. 配列は普通のクラス val ints: Array<Int> = arrayOf<Int>(0) val anys: Array<Any> =

    ints anys[0] = "Hello" JavaのObjectクラス的ポジション
  19. 配列は普通のクラス val ints: Array<Int> = arrayOf<Int>(0) val anys: Array<Any> =

    ints anys[0] = "Hello" コンパイルエラー! Javaと同様、ジェネリッククラスは 型引数に対してデフォルトで「不変」
  20. 共変 <? extends T>的なやつ val ints: List<Int> = listOf(1, 2,

    3) val anys: List<out Any> = ints KotlinのListインタフェースはイミュータブル 型パラメータの型を引数にとるようなメソッドを提供していない out指定は冗長
  21. 便利な「宣言場所変位指定」 val ints: List<Int> = listOf(1, 2, 3) val anys:

    List< Any> = ints Listインタフェースの定義 interface List<out E> { … } out指定を省略可
  22. 高階関数と関数型 fun <A, B> map(list: List<A>, f: (A)->B): List<B> {

    val result = mutableListOf<B>() for(e in list) { result += f(e) } return result }
  23. 高階関数と関数型 fun <A, B> map(list: List<A>, f: (A)->B): List<B> {

    val result = mutableListOf<B>() for(e in list) { result += f(e) } return result } 関数 f で、List<A>を変換してList<B>を得る関数
  24. 高階関数と関数型 fun <A, B> map(list: List<A>, f: (A)->B): List<B> {

    val result = mutableListOf<B>() for(e in list) { result += f(e) } return result } 「Aを取ってBを返す関数」の型
  25. 演算子オーバロード fun <A, B> map(list: List<A>, f: (A)->B): List<B> {

    val result = mutableListOf<B>() for(e in list) { result += f(e) } return result } MutableListクラスの addメソッドに対応
  26. ラムダ式 val list = listOf(1, 2, 3) map(list, { i:

    Int -> i * i }) //=> [1, 4, 9] map(list, { i -> i * i }) map(list, { it * it }) map(list) { it * it }
  27. ラムダ式 val list = listOf(1, 2, 3) map(list, { i:

    Int -> i * i }) //=> [1, 4, 9] map(list, { i -> i * i }) map(list, { it * it }) map(list) { it * it } ←引数の型を省略可
  28. ラムダ式 val list = listOf(1, 2, 3) map(list, { i:

    Int -> i * i }) //=> [1, 4, 9] map(list, { i -> i * i }) map(list, { it * it }) map(list) { it * it } ←暗黙の変数 it
  29. ラムダ式 val list = listOf(1, 2, 3) map(list, { i:

    Int -> i * i }) //=> [1, 4, 9] map(list, { i -> i * i }) map(list, { it * it }) map(list) { it * it } ←ラムダ式を外出し構文糖衣
  30. ラムダ式 val list = listOf(1, 2, 3) map(list, { i:

    Int -> i * i }) //=> [1, 4, 9] map(list, { i -> i * i }) map(list, { it * it }) map(list) { it * it } map(list) {...}ってイケてなくない?
  31. 拡張関数 fun <A, B> List<A>.map(f: (A)->B): List<B> { val result

    = mutableListOf<B>() for(e in this) { result += f(e) } return result }
  32. 拡張関数 fun <A, B> List<A>.map(f: (A)->B): List<B> { val result

    = mutableListOf<B>() for(e in this) { result += f(e) } return result } List<A>に対してmapというメソッドを生やすイメージ
  33. 拡張関数 fun <A, B> List<A>.map(f: (A)->B): List<B> { val result

    = mutableListOf<B>() for(e in this) { result += f(e) } return result } メソッドのように、自分自身への参照はthis
  34. Java 6 対応のバイトコードを吐く • ラムダ式の実行に、InvokeDynamicは使用されない • 関数オブジェクトを生成 ◦ オブジェクト生成&GCのコスト ◦

    単純なラムダ式は、シングルトンオブジェクトとなる ◦ クロージャとなるラムダ式は、毎回 生成される
  35. インライン関数 inline fun <A, B> List<A>.map(f: (A)->B): List<B> { val

    result = mutableListOf<B>() for(e in this) { result += f(e) } return result } • ラムダ式を含め、呼び出し元に実装がインライン展開される • 呼び出しオーバヘッドなし
  36. プロパティ class User(val name: String, val id: Long) val user

    = User("Taro", 123) user.id //=> 123 user.name //=> "Taro"
  37. プロパティ class User(val name: String, val id: Long) val user

    = User("Taro", 123) user.id //=> 123 user.name //=> "Taro" インスタンス生成 newキーワード不要
  38. プロパティ class User(val name: String, val id: Long) val user

    = User("Taro", 123) user.id //=> 123 user.name //=> "Taro" プロパティ →単純なgetter/setter地獄から解放される Javaのフィールドのように見 えるが、実際には内部状態 (実装)とAPIを分けて持ってい る。必要なら実装のカスタマイ ズが可能。
  39. デフォルト引数 class User(val name: String, val id: Long? = null)

    val user = User("Taro") デフォルト値が設定されている 引数は省略可
  40. 名前付き引数 class User(val name: String, val id: Long? = null)

    val user = User(id = 123, name = "Taro") 名前で引数を指定できる →順序を入れ替えられる →引数が増えても混乱しない
  41. データクラス data class User(val name: String, val id: Long? =

    null) • 修飾子dataを付けるだけ • equals, hashCode, toStringが手に入る • copyメソッドが手に入る • componentNメソッドが手に入る
  42. equals, hashCode, toString val user = User("Taro") user == User("Taro")

    //=> true user.toString() //=> User(name=Taro, id=null)
  43. copyメソッド val user = User("Taro") val user2 = user.copy(id =

    123) user2.toString() //=> User(name=Taro, id=123) user === user2 //=> false
  44. val user = User("Taro") val user2 = user.copy(id = 123)

    user2.toString() //=> User(name=Taro, id=123) user === user2 //=> false copyメソッド 変更したいプロパティを 指定して、コピーを得る
  45. val user = User("Taro") val user2 = user.copy(id = 123)

    user2.toString() //=> User(name=Taro, id=123) user === user2 //=> false copyメソッド コピーなので、異なるインスタンス
  46. データクラス簡単&便利 data class User( name: String, id: Long? = null

    ) class User { private final Long id; private final String name; public User(final Long id, final String name) { this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final User user = (User) o; if (!id.equals(user.id)) return false; return name.equals(user.name); } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + name.hashCode(); return result; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
  47. Null安全 val s1: String = null val s2: String? =

    null s2.toUpperCase() if(s2 != null) s2.toUpperCase() s2?.toUpperCase()
  48. Null安全 val s1: String = null // NG val s2:

    String? = null s2.toUpperCase() if(s2 != null) s2.toUpperCase() s2?.toUpperCase() 通常の型の変数には、nullを代入できない
  49. Null安全 val s1: String = null // NG val s2:

    String? = null // OK s2.toUpperCase() if(s2 != null) s2.toUpperCase() s2?.toUpperCase() ?付きの型の変数には、nullを代入可
  50. Null安全 val s1: String = null // NG val s2:

    String? = null // OK s2.toUpperCase() // NG if(s2 != null) s2.toUpperCase() s2?.toUpperCase() ?な参照は、そのままデリファレンスできない →ぬるぽる危険性があるから
  51. Null安全 val s1: String = null // NG val s2:

    String? = null // OK s2.toUpperCase() // NG if(s2 != null) s2.toUpperCase() // OK s2?.toUpperCase() nullでないことが保証される文脈では デリファンレス可能
  52. Null安全 val s1: String = null // NG val s2:

    String? = null // OK s2.toUpperCase() // NG if(s2 != null) s2.toUpperCase() // OK s2?.toUpperCase() // OK 安全呼び出し: s2がnullなら、何もせず直ちにnullを返す
  53. let + Null安全 fun reverse(s: String): String {...} val reverse:

    String? = str?.let { reverse(it) } fun findById(id: Long): User? {...} val user: User? = userId?.let { findById(it) } user?.let { println(it }
  54. let + Null安全 fun reverse(s: String): String {...} val reverse:

    String? = str?.let { reverse(it) } fun findById(id: Long): User? {...} val user: User? = userId?.let { findById(it) } user?.let { println(it } Optional#map的な役割
  55. let + Null安全 fun reverse(s: String): String {...} val reverse:

    String? = str?.let { reverse(it) } fun findById(id: Long): User? {...} val user: User? = userId?.let { findById(it) } user?.let { println(it } Optional#flatMap的な役割 (※)
  56. let + Null安全 fun reverse(s: String): String {...} val reverse:

    String? = str?.let { reverse(it) } fun findById(id: Long): User? {...} val user: User? = userId?.let { findById(it) } user?.let { println(it } Optional#ifPresent的な役割 (※)
  57. Kotlinを実際に使ってみたよ • 趣味: 某アプリ ◦ Android ◦ Dagger2, Realm, KotterKnife

    • 業務: 医薬品情報アプリ ◦ Android ◦ Dagger2, Android Extensions • 業務: 社内用 Web API ◦ Spring Boot ◦ JPA, Swagger
  58. イマイチだった点 • JPAやRealmのエンティティクラスはちょっとしんどい ◦ アノテーションぺたぺた貼る作業 ◦ デフォルトコンストラクタが必要だったり ◦ valにしたいのにvarにせざるを得ない ◦

    nullを許容したくないのに、?を付けざるを得ない ◦ ここだけJavaで書くのもアリか • Java用FWやツールを使う場合は、どうしてもJavaを意識せざ るを得ない
  59. まとめ • Javaやそのエコシステムは便利だけどJava言語つらい • JVM言語が台頭してきた • その一つが、JetBrains製のKotlin • 敷居が低そうでAndroidサポートもしてるしよさげ •

    定型コードを減らし、シンプルに保てる ◦ ラムダ式、拡張関数、データクラスなど • 安全性を意識した設計 ◦ Null安全、型安全 • 実際使ってみると、案の定良い点が多かった • 現時点で課題は残るのも確か。Javaとの共存で回避可能