Keynote&Developer Keynote
Keynote
KeynoteにおいてKotlinはAndroid開発における公式言語としてサポートされることが発表されました。
合わせて、
- JetBrains社とパートーナーとしてKotlin財団の設立
- 既存のコード(Java)にもさらなる投資をする
ことが発表されています。
Kotlinが公式言語としてサポートされるに至った背景についてKeynote内では以下のような項目があげられています。
- KotlinはAndroidの開発者コミュニティが既に求めていた言語で、開発者の生産性を高めることが出来る
- Androidのランタイムとの完全な互換性があり、既存のコードとの相互運用が可能である
- 素晴らしいIDEによるサポート
- 言語が成熟し、製品版としてリリース可能な状態である
Developer Keynote
Developer KeynoteではAndroid/Google Playにおいて大きな4つのテーマについて話がされました。
- Kotlin
- Android Studio & Libraries
- App Quality & Success
- Android Instant App
そのうちの一つとしてKotlinは取り上げられ、Kotlinは「first-class language」としてサポートされ、Android StudioにKotlinが組み込まることになりました。
ただし、Java、C++への投資は引き続き変わらず行われ、追加としてKotlinはサポートされます。
Developer Keynote内で2つのKotlinに関するセッションが発表されました。
- Introduction to Kotlin
- Life is Great and Everything Will Be Ok Kotlin is Here
- スライド : https://speakerdeck.com/jakewharton/life-is-great-and-everything-will-be-ok-kotlin-is-here-google-io-2017
- 動画 : https://www.youtube.com/watch?v=fPzxfeDJDzY
Introduction to Kotlin
概要
前半はJetBrains社Hadi Hariri氏によるライブコーディング、後半は同じくJetBrains社のKotlin言語リード開発者であるAndrey Breslav氏によるプレゼンテーションでした。
Keynoteと同じ会場が8割ほど埋まるほど大盛況のセッションとなりました。
Introduction to Kotlin #IO17 pic.twitter.com/KhOxsqFDnH
— Satoru Fujiwara (@satorufujiwara) 2017年5月19日
特に前半のライブコーディングはこれからKotlinでAndroid開発を始める人にとっては必見の内容となっています。
セッションの内容はYoutubeで見ることが出来ます。
https://www.youtube.com/watch?v=X1RVYt2QKQE
本記事では特に前半のライブコーディングの部分について和訳・要約をしました。
動画を見る上での参考にしていただければ幸いです。
ライブコーディング by Hadi Hariri
Kotlinを使って、どう書くか?どういう利点があるかの紹介をします。
まずはdata classの紹介です。
amount
とcurrency
という2つのread only propertyを持ったdata classを定義します。
data class Money(val amount: Int, val currency: String)
これをJavaで書くと以下のようになります。
public class JavaMoney {
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "JavaMoney{" +
"amount=" + amount +
"currency='" + currency + '\'' +
'}';
}
public JavaMoney(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
private int amount;
private String currency;
public int getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
}
read only propertyなのでJavaの場合はgetterしかありません。
toString
、clone
(Kotlinの場合はcopy
)、equals
、hashCode
と4つのメソッドも定義されています。
これらのメソッドはIDEが自動生成してくれたりしますが、メンテンナンス時に変更が必要になったときには変更に関連するコードを全て変更していく必要があります。
次にエントリポイントとなるfunctionを定義します。
fun main(args: Array<String>) {
}
Javaではstaticなメソッドをclass内に定義する必要がありますが、Kotlinはその必要はありません。
JavaScriptのように(もっと良い形で)、トップレベルに定義することが出来ます。
これはfunctionに限らず、propertyやclassもいかなる場所に定義することが出来ます。
val tickets = Money(100, "$")
tickets
という名のインスタンスを定義します。
Kotlinは型推論があるので、型を明記する必要はありません。
val popcorn = tickets.copy(500, "EUR")
copy
を使ってpopcorn
という新しいインスタンスを定義します。
同じpropertyを持つ場合はcopy
には引数が必要ありませんが、違う値をpropetryに代入したい場合はcopy
の引数に渡すことが出来ます。
if (tickets != popcorn) {
println("They are different!")
}
!=
を使ってインスタンスの持つpropertyそれぞれが全て等しいかの比較が出来ます。
同じオブジェクトかどうかの比較は!==
を用います。
val javaMoney = JavaMoney(100, "$")
javaMoney.amount
KotlinからJavaを呼ぶことが出来ます。
JavaでのgetterはKotlinからはpropertyのように変換されて呼び出すことが出来ます。
Money money = new Money(100, "$");
money.getAmount();
JavaからのKotlinを呼ぶことが出来、KotlinのpropertyはJavaからはgetterのように見えます。
このように相互に呼び出せることによって同じプロジェクトでJavaとKotlinが共存出来ます。
fun sendPayment(money: Money) {
println("Sending ${money.amount}")
}
sendPayment
というfunctionをトップレベルに定義します。
Kotlinのfunctionは戻り値が特にない場合はvoid
のように定義する必要がなく、デフォルトはUnit
というシングルトンのクラスが返されます。
fun sendPayment(money: Money, message: String = "") {
println("Sending ${money.amount}")
}
functionの引数にはデフォルト値を設定することが出来ます。
また呼び出し時には名前をつけて呼び出すことが出来ます。
sendPayment(message = "Good luck!", money = tickets)
どの引数に何を渡してるかがわかりやすくなります。
fun sum(x: Int, y: Int) = x + y
一行でfunctionを定義することが出来、戻り値も暗黙的に定義されるので明記する必要はありません。
fun convertToDollars(money: Money): Money {
when (money.currency) {
"$" -> return money
"EUR" -> return Money(money.amount * BigDecimal(1.10), "$")
else -> throw IllegalArgumentException("not the currency you're interested")
}
}
上記のようなfunctionも以下のように一行で書くことが出来ます。
fun convertToDollars(money: Money) = when (money.currency) {
"$" -> money
"EUR" -> Money(money.amount * BigDecimal(1.10), "$")
else -> throw IllegalArgumentException("not the currency you're interested")
}
ここ以降、data classを以下のように変更しています。
data class Money(val amount: BigDecimal, val currency: String)
拡張関数を用いればBigDecimal
のような既存のclassにfunctionを増やすことが出来ます。
fun BigDecimal.percent(percentage: Int) = this.multiply(BigDecimal(percentage)).divide(BigDecimal(100))
以下のように呼び出します。
val bd1 = BigDecimal(100)
bd1.percent(7)
infix fun Int.percentOf(money: Money) = money.amount.multiply(BigDecimal(this)).divide(BigDecimal(100))
Int
などのクラスも拡張することが出来、infix
を用いることで、以下のように呼び出すことが出来ます。
7 percentOf popcorn
private val Int.bd: BigDecimal
get() = BigDecimal(this)
拡張関数と似たような形で拡張propertyを用いれば以下のようにlong
と似た形でBigDecimal
のインスタンスをつくることが出来ます。
val long = 100L
val bd2 = 100.bd
Kotlinでは+
,-
,*
などのオペレーターをオーバーライドすることが出来ます。
operator fun Money.plus(money: Money) =
if (currency == money.currency) {
Money(amount + money.amount, currency)
} else
throw IllegalArgumentException("We're gonna have a problem here!")
plus
という名前のoperator functionをオーバーライドすれば以下のように呼び出すことが出来ます。
val costs = tickets + popcorn
val train: Money = Money(100.bd, "$")
train = null // error!
新しいインスタンスを定義してnull
を代入しようとするとエラーとなります。
これは正確には2つの意味でエラーとなっています。
val
はimmutable(不変)を表現していて、一度値を代入しようとすることは出来ません。
以下のようにvar
に置き換えることでmutableにすることが出来ます。
var train: Money = Money(100.bd, "$")
train = null // error!
Kotlinにおいてはデフォルトの型がnullableではないので、var
に置き換えてもエラーが出ます。
Money?
と?
をつけることでnullableな型を宣言出来ます。
Kotlinの場合はnullableな型を持ちたくないので、ほとんどこうする必要がないでしょう。
しかし、KotlinはJavaとの互換性があり、Javaの型はnullableです。
fun javaMoney(money: JavaMoney?) {
println("${money.amount} is valid") // error!
}
nullableな型を引数にとるfunctionを定義して、その引数のpropertyを呼びだそうとするとエラーとなります。
これを解決するには2つの方法があります。
1つ目はnullチェックをすることです。
fun javaMoney(money: JavaMoney?) {
if (money != null) {
println("${money.amount} is valid")
}
}
(動画では述べられてませんがここには後述のsmart castが使われています。)
2つ目は?
を使ってpropertyを呼び出すことです。
fun javaMoney(money: JavaMoney?) {
println("${money?.amount} is valid")
}
楽しみたいのなら別の方法もあります。
fun javaMoney(money: JavaMoney?) {
println("${money!!.amount} is valid")
}
!!
というオペレーターはnullであることを知りながらも墓穴を掘りたい時に使います。
次はhigher-order function(高階関数)についての話をします。
fun findEmails(users: List<User>, predicate: (String) -> (Boolean)): List<User> {
TODO("LATER!")
}
上記のようなfunctionを定義します。
余談ですがTODO
は呼び出すとNotImplementedError
を投げる関数として用意されています。
data class User(val id: Int, val username: String, val email: String, val role: Role)
enum class Role {
Admin,
Regular
}
fun usersFromJSONfile(fileName: String): List<User> {
val gson = Gson()
return gson.fromJson<List<User>>(FileReader(fileName), object : TypeToken<List<UserResult>>() {}.type)
}
以上のようなdata classとfunctionを用意してfindEmails
を呼び出します。
val users = usersFromJSONfile("users.json")
findEmails(users, { value -> value.endsWith(".com") })
lambdaを第二引数にとり、emailが.comで終わるユーザーを見つけることが出来ます。
lambdaの引数が一つの場合、明示的に宣言する必要はなく、it
を使って引数を呼び出せることが出来ます。
findEmails(users, { it.endsWith(".com") })
さらに、functionの最後の引数がlambdaの場合、()
の中に入れる必要はありません。
findEmails(users) {
it.endsWith(".com")
}
以上のようなことは改めて実装する必要はなく、build-inのfunctionを使って実装することが出来ます。
val dotComUsers = users.filter { it.email.endsWith(".com") }
.sortedBy { it.id }
.map { Pair(it.email, it.username) }
email.が.comで終わるユーザを見つけ、idでソートし、emailとusernameのpairにしています。
これらのfunctionはとても小さいライブラリとしてリリースされており、Androidにおいても全く問題なく使うことが出来ます。
Pairはto
というfunctionを使うことでより良く書けます。
val dotComUsers = users.filter { it.email.endsWith(".com") }
.sortedBy { it.id }
.map { it.email to it.username }
data classとして定義されたclassはdestructuring-declarationを使うことが出来ます。
val (id, username, email) = users.filter { it.email.endsWith(".com") }
.sortedBy { it.id }
.first()
println(id)
使わない変数は_
に置き換えましょう。
val (id, _, _) = ...
次にsealed classについて紹介します。
まず、Kotlinのclassはデフォルトでは継承できません。
open
をつける必要があります。
class UserResult
data class Success(val users: List<User>) : UserResult() // error!
data class Failure(val message: String) : UserResult() // error!
open class UserResult
UserResultをsealed classとして宣言します。
sealed class UserResult
data class Success(val users: List<User>) : UserResult()
data class Failure(val message: String) : UserResult()
sealed classはclassのヒエラルキーを表現したクラスで、UserResultを継承したclassが上記以外にないことを示しています。
(enumの拡張のようなものです。)
継承したclassは同じfileに書く必要があります。
sealed class UserResult {
data class Success(val users: List<User>) : UserResult()
data class Failure(val message: String) : UserResult()
}
上記のようにネストして書くことも出来ますがUserResult.Success
のように書く必要があるので、ここではしません。
(Kotlinの1.1までは上記のようにネストして書く必要があった。)
このclassは成功時と失敗時で違ったpropertyを持つclassを返したい時に使えます。
fun retrieveUsers(): UserResult {
return Success(usersFromJSONfile("users.json"))
}
val result = retrieveUsers()
when (result) {
is Success -> result.users.forEach { println(it.username) }
is Failure -> println(result.message)
}
when
の中のis Success
以降のresult
はsmart castされており、Success
という型にcastされています。
is Failure
以降のresult
も同様にFailure
にcastされています。
ここまではeager evaluation(先行評価)の話でしたが、lazy evaluation(遅延評価)の例を紹介します。
val values = generateSequence(1) {
it * 10
}
values.take(10).forEach { println(it) }
generateSequence
で作られるsequenceは無限で終わりがありませんが、take(10)
で最初から10件だけ取得し、それらを出力しています。
val users = usersFromJSONfile("users.json").asSequence()
asSequence
を用いることでlazy evaluationへ置換することが出来ます。
プレゼンテーション by Andrey Breslav
将来、Kotlinがどのようになっていくかの話をします。
1つめの大きな方向はKotlinが様々なプラットフォームをサポートする方向に向かっています。
JVM上、Android、JavaScript(ブラウザ、node.js)でKotlinは動きますが、テクニカルプレビュー版として出しているKotlin/NativeはiOS、Linux、MAC、Windowsといったプラットフォーム上のコードにコンパイルされ、動作するように作っています。
1つのウェブアプリケーションがサーバサイドもクライアントもKotlinで書かれて、共通化のコードがそれらの間でシェア出来るようにマルチプラットフォームを目指しています。
2つめはCoroutinesです。
非同期的なプログラミングから、コールバックや複雑なフローを取り除き、sequentialなコードで書けることを目指しています。
Kotlinを始めたい場合は以下のサイトからはじめてください。
-
Official Kotlin Site
kotlinlang.org or kotl.in -
Kotlin for Android Resource
kotl.in/android