Slide 1

Slide 1 text

PragmaticなJVM言語のルーキー Kotlinってなんだ? 2016-04-08 ビズリーチ社内勉強会 長澤 太郎 @ngsw_taro

Slide 2

Slide 2 text

Kotlinとは何者か?

Slide 3

Slide 3 text

Kotlinの何が 素晴らしいのか?

Slide 4

Slide 4 text

簡潔 安全 Interop

Slide 5

Slide 5 text

Pragmatic 実用的、実践的

Slide 6

Slide 6 text

(゚Д゚)ハァ?

Slide 7

Slide 7 text

PragmaticなJVM言語のルーキー Kotlinってなんだ? 2016-04-08 ビズリーチ社内勉強会 長澤 太郎 @ngsw_taro

Slide 8

Slide 8 text

本日のハッシュタグ #biz_kt

Slide 9

Slide 9 text

● 長澤 太郎 たろーって呼んでね ● @ngsw_taro ● プログラマー@エムスリー株式会社 ○ Android, Kotlin, Java, Scala, Rubyなど ● Kotlinエバンジェリスト(JetBrains黙認) ● やすべえとディズニーが好き 自己紹介

Slide 10

Slide 10 text

エバンジェリストな私 ● Kotlin歴 4年 ● 日本Kotlinユーザグループ代表 ● 講演実績多数 ○ DroidKaigi 2015, 2016 ○ JJUG CCC 2015 Fall ○ Kotlin勉強会@Sansan (2回) ○ 関西Kotlin勉強会 (3回) ● 執筆実績多数 ○ 商業誌 x 3, 同人誌 x 1 ○ Kotlin本 目下執筆中!

Slide 11

Slide 11 text

● 日本・世界の医療改善を目指し医療関連のサービスを開発・ 運営 ● Androidアプリ開発でKotlinを利用している ● 開発エンジニア、常に募集中! エムスリーの紹介

Slide 12

Slide 12 text

もくじ 1. なぜKotlinなのか? 2. Kotlinのここがスゴイ! 3. Kotlinを使ってみて

Slide 13

Slide 13 text

1. なぜKotlinなのか?

Slide 14

Slide 14 text

私自身、Java大好き ● 初めて触った言語は、Java ● 一番得意な言語は、Java ● 日本Javaユーザグループ 運営スタッフ

Slide 15

Slide 15 text

Javaっていいよね ● 進化を続けるプログラミング言語 ○ しかも後方互換性を維持 ● 歴史長い、人口多い ○ ライブラリ・フレームワークが多い ○ 知見が多い ● 高性能・高信頼性のJava仮想マシン

Slide 16

Slide 16 text

しかしその反面、問題も... ● 冗長、ボイラープレートの嵐 ● nullポインタのデリファレンス問題 ● 後方互換性の維持 ○ 古い文法が存在する ○ 型安全性に問題あり

Slide 17

Slide 17 text

Slide 21

Slide 21 text

ボイラープレート 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

Slide 22

Slide 22 text

ボイラープレート 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のようなビルダーパ ターンを実装

Slide 23

Slide 23 text

ボイラープレート 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 が存在する

Slide 24

Slide 24 text

nullポインタのデリファレンス問題 String s = null; s.toUpperCase();

Slide 25

Slide 25 text

nullポインタのデリファレンス問題 String s = null; s.toUpperCase(); 当然、ぬるぽります。

Slide 26

Slide 26 text

ぬるぽが嫌ならnullチェックをすれば(ry String s = なんらかのメソッド(); String got = (s != null) ? s.toUpperCase() : null;

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

それ本当? 実際、本当 String s = reverse("Hello"); String got = (s != null) ? s.toUpperCase() : null; でも、そんなことしたら 余計に冗長になるだけ nullは返し得ないメソッド

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Java的な工夫 @Nonnull String reverse(String s) {...} Optional findUserById(long id) {...}

Slide 32

Slide 32 text

誰にもnullは止められない! @Nonnull Optional 絶対にnullは返さないメソッド() { return null; }

Slide 33

Slide 33 text

誰にもnullは止められない! @Nonnull Optional 絶対にnullは返さないメソッド() { return null; } javac「誰だ!?」 null 「Optionalです」 javac「よし!通れ!」

Slide 34

Slide 34 text

後方互換: 古い文法が存在する List users = new ArrayList<>();

Slide 35

Slide 35 text

後方互換: 古い文法が存在する List users = new ArrayList<>(); ベテラン「ダイヤモンド演算子よい」 初心者「<>もなければスッキリするのになぁ」

Slide 36

Slide 36 text

後方互換: 古い文法が存在する List users = new ArrayList<>(); ベテラン「ダイヤモンド演算子よい」←訓練されたJavaer 初心者「<>もなければスッキリするのになぁ」←正しい

Slide 37

Slide 37 text

後方互換: 古い文法が存在する List users = new ArrayList<>(); ベテラン「ダイヤモンド演算子よい」←訓練されたJavaer 初心者「<>もなければスッキリするのになぁ」←正しい new ArrayList() だと いにしえの raw type になるんだよ><

Slide 38

Slide 38 text

後方互換: 型安全性に問題あり Integer[] ints = { 0 }; Object[] objs = ints; objs[0] = "Hello";

Slide 39

Slide 39 text

後方互換: 型安全性に問題あり Integer[] ints = { 0 }; Object[] objs = ints; objs[0] = "Hello"; コンパイルは通るがクラッシュする Effective Java 項目25 「配列よりリストを選ぶ」

Slide 40

Slide 40 text

つらみまとめ ● 冗長、ボイラープレートの嵐 ● nullポインタのデリファレンス問題 ● 後方互換性の維持 ○ 古い文法が存在する ○ 型安全性に問題あり とはいえ 良いところもたくさんあるので 今後も選択肢として有力な言語だ と思ってます。

Slide 41

Slide 41 text

Java代替言語の台頭 ● Scala ● Groovy ● Clojure ● Kotlin

Slide 42

Slide 42 text

Kotlinとは ● 静的型付けオブジェクト指向言語 ● 開発元: JetBrains ● 2011年7月発表、2016年2月正式リリース ● Apache License ver 2.0

Slide 43

Slide 43 text

Kotlinを選ぶ理由 ● Javaよりも安全、簡潔なコードを書ける ● 実用的な表現力を維持しつつ、Scalaよりも簡潔 ○ JetBrains曰く... ● Javaエンジニアにとって、学習コストが低そう ○ 精神的な敷居もやや低めっぽい ● JetBrainsの後ろ盾がある ● 公式によるAndroidのサポート ○ かなり真剣に取り組んでいる模様

Slide 44

Slide 44 text

2. Kotinのここがスゴイ!

Slide 45

Slide 45 text

Hello World fun main(args: Array) { println("Hello, world!") }

Slide 46

Slide 46 text

Hello World fun main(args: Array) { println("Hello, world!") } 関数定義のキーワード エントリポイント 型は後置 配列が ジェネリクス セミコロン 不要 パッケージ直下に 関数を定義できる

Slide 47

Slide 47 text

配列は普通のクラス val ints: Array = arrayOf(0) val anys: Array = ints anys[0] = "Hello"

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

配列は普通のクラス val ints: Array = arrayOf(0) val anys: Array = ints anys[0] = "Hello" コンパイルエラー! Javaと同様、ジェネリッククラスは 型引数に対してデフォルトで「不変」

Slide 51

Slide 51 text

配列は普通のクラス val ints = arrayOf (0) val anys: Array = ints anys[0] = "Hello" 型推論

Slide 52

Slide 52 text

共変 extends T>的なやつ val ints: List = listOf(1, 2, 3) val anys: List = ints

Slide 53

Slide 53 text

共変 extends T>的なやつ val ints: List = listOf(1, 2, 3) val anys: List = ints KotlinのListインタフェースはイミュータブル 型パラメータの型を引数にとるようなメソッドを提供していない out指定は冗長

Slide 54

Slide 54 text

便利な「宣言場所変位指定」 val ints: List = listOf(1, 2, 3) val anys: List< Any> = ints Listインタフェースの定義 interface List { … } out指定を省略可

Slide 59

Slide 59 text

ラムダ式 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 }

Slide 60

Slide 60 text

ラムダ式 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 } ←引数の型を省略可

Slide 61

Slide 61 text

ラムダ式 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

Slide 62

Slide 62 text

ラムダ式 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 } ←ラムダ式を外出し構文糖衣

Slide 63

Slide 63 text

ラムダ式 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) {...}ってイケてなくない?

Slide 67

Slide 67 text

メソッドのように使える val list = listOf(1, 2, 3) list.map { it * it } //=> [1, 4, 9]

Slide 68

Slide 68 text

Java 6 対応のバイトコードを吐く ● ラムダ式の実行に、InvokeDynamicは使用されない ● 関数オブジェクトを生成 ○ オブジェクト生成&GCのコスト ○ 単純なラムダ式は、シングルトンオブジェクトとなる ○ クロージャとなるラムダ式は、毎回 生成される

Slide 70

Slide 70 text

ここまでまとめ&小休憩 ● Hello Worldに「おまじない」が少ない ● 配列は普通のクラスで、型安全 ● クラスやインタフェース定義時に型パラメータの変位を指定可 ● 演算子オーバロード ● ラムダ式 ● 拡張関数 ● インライン関数で、呼び出しオーバヘッドなし

Slide 71

Slide 71 text

クラス class User(val name: String, val id: Long)

Slide 72

Slide 72 text

クラス class User(val name: String, val id: Long) プライマリコンストラクタ

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

デフォルト引数 class User(val name: String, val id: Long? = null) 引数のデフォルト値

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

データクラス data class User(val name: String, val id: Long? = null) ● 修飾子dataを付けるだけ ● equals, hashCode, toStringが手に入る ● copyメソッドが手に入る ● componentNメソッドが手に入る

Slide 80

Slide 80 text

equals, hashCode, toString val user = User("Taro") user == User("Taro") //=> true user.toString() //=> User(name=Taro, id=null)

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

val user = User("Taro") val user2 = user.copy(id = 123) user2.toString() //=> User(name=Taro, id=123) user === user2 //=> false copyメソッド コピーなので、異なるインスタンス

Slide 84

Slide 84 text

componentNメソッド val user = User("Taro", 123) user.component1() //=> "Taro" user.component2() //=> 123 val (name, id) = user name //=> "Taro" id //=> 123

Slide 85

Slide 85 text

componentNメソッド val user = User("Taro", 123) user.component1() //=> "Taro" user.component2() //=> 123 val (name, id) = user name //=> "Taro" id //=> 123

Slide 86

Slide 86 text

データクラス簡単&便利 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 + '\'' + '}'; } }

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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を返す

Slide 93

Slide 93 text

便利な標準ライブラリ関数 let inline fun T.let(f: (T) -> R): R = f(this)

Slide 94

Slide 94 text

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 }

Slide 95

Slide 95 text

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的な役割

Slide 96

Slide 96 text

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的な役割 (※)

Slide 97

Slide 97 text

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的な役割 (※)

Slide 98

Slide 98 text

letは便利だけど、複数あると... foo?.let { foo -> bar?.let { bar -> baz?.let { baz -> execute(foo, bar, baz) } } }

Slide 99

Slide 99 text

素直にifでチェックしましょう if (foo != null && bar != null && baz != null) { execute(foo, bar, baz) }

Slide 100

Slide 100 text

3. Kotinを使ってみて

Slide 101

Slide 101 text

Kotlinを実際に使ってみたよ ● 趣味: 某アプリ ○ Android ○ Dagger2, Realm, KotterKnife ● 業務: 医薬品情報アプリ ○ Android ○ Dagger2, Android Extensions ● 業務: 社内用 Web API ○ Spring Boot ○ JPA, Swagger

Slide 102

Slide 102 text

体感としては 気持ちいい 捗る 最高

Slide 103

Slide 103 text

医薬品情報アプリ: MR君 ● 昔誰かが作ったものを、Kotlinでリニューアル ● 17060行→7197行 ○ 57.8% 削減(機能増にも関わらず) ○ エンジニア(=私)が優秀という要因もあるか

Slide 104

Slide 104 text

よかった点 ● FWやプラットフォームのための定型コードは仕方ないとして、 ロジック部分はすごく書きやすい ● ロジック部分で一度もぬるぽってない(記憶によると) ● 既存ライブラリやAPIの使い勝手が悪い場合は、拡張関数を 定義してKotlinフレンドリに ● Java用ライブラリやツールが動いてくれる ○ Dagger2, Realm, Swaggerなど

Slide 105

Slide 105 text

イマイチだった点 ● JPAやRealmのエンティティクラスはちょっとしんどい ○ アノテーションぺたぺた貼る作業 ○ デフォルトコンストラクタが必要だったり ○ valにしたいのにvarにせざるを得ない ○ nullを許容したくないのに、?を付けざるを得ない ○ ここだけJavaで書くのもアリか ● Java用FWやツールを使う場合は、どうしてもJavaを意識せざ るを得ない

Slide 106

Slide 106 text

まとめ ● Javaやそのエコシステムは便利だけどJava言語つらい ● JVM言語が台頭してきた ● その一つが、JetBrains製のKotlin ● 敷居が低そうでAndroidサポートもしてるしよさげ ● 定型コードを減らし、シンプルに保てる ○ ラムダ式、拡張関数、データクラスなど ● 安全性を意識した設計 ○ Null安全、型安全 ● 実際使ってみると、案の定良い点が多かった ● 現時点で課題は残るのも確か。Javaとの共存で回避可能

Slide 107

Slide 107 text

Thank you Enjoy Kotlin!