25日目:まとめとこれから
メリークリスマス!!!!
今日はクリスマス!12月1日から昨日まで毎日記事を投稿し、みなさんへKotlinの面白さをお伝えしてきました。そんな当アドベントカレンダーも今日で最終回です。Kotlinを学んだり貢献したりするためのヒントと、Kotlinのこれからについて、それと全体のまとめを示して終わりにしたいと思います。
参考資料
- http://kotlin.jetbrains.org/
- Kotlinに関する各種資料へリンクが張ってあるKotlinの公式サイトです。
- http://blog.jetbrains.com/kotlin/
- Kotlinプロジェクトチームのブログです。マイルストーンのリリースはこちらで発表されます。
- http://confluence.jetbrains.net/display/Kotlin/
- Kotlinの公式ドキュメントです。ただし正確な言語仕様ではないことに注意してください。
- http://devnet.jetbrains.net/community/kotlin
- Kotlinのフォーラムです。質問を投げることができます。
- http://taro.hatenablog.jp/
- 私の技術ブログです。Kotlinについて書くことがあります。
貢献(Kontribute)する
Kotlinはオープンソースソフトウェアなので、誰でもソースコードを見たり編集したりすることができます。ソースコードはGithub上で公開されています。コードを修正、改善したらPull Requestを送りましょう。
Kotlinにバグ等を発見した場合にはKotlinのBTSにissueを投げましょう。中の人や他の貢献者が修正してくれるかも知れません。もちろん自身で修正してもOKです。
また、貢献は言語開発に限定されるものではありません。KotlinによるKotlinのためのライブラリやフレームワークを作って公開するのも重要な貢献です。それからブログや勉強会での発表でKotlinを啓蒙することも貢献のひとつだと思います。
Kotlinの今後
Kotlinはまだ開発途上にある言語です。実装されていない言語機能もありますし、標準ライブラリもまだまだ発展の余地はあります。Kotlin 1.0 のリリースに向けて、それらは日々進化しています。その他にはモジュールシステム*1やEclipse用プラグインの登場が待たれます。
全体のまとめ
Kotlinの概要紹介から始まり、基本構文、関数型プログラミング的アプローチ、オブジェクト指向的アプローチ、ライブラリ/フレームワークとの連携例を紹介してきました。これらを通して改めて感じることは、Kotlinは
- 少ない記述量で多くを表現できる
- より安全な方向へプログラマを導く
言語であるということです。これら2つの特長は、ソフトウェアが複雑になればなるほど、その効果が顕著に現れることでしょう。それでいて敷居の高さを感じさせない文法やルールを採っています。Kotlinに可能性を感じずにはいられません。
おわりに
一人アドベントカレンダーは初めての挑戦な上に、やろうと思ったのが11月最後の週だったので書き溜めはおろか、構想も十分に練っていない状態で始めました。なんとか毎日記事を投稿できて安心しています。みなさんにちょっとでもKotlinの面白さ、かわいさが伝われば嬉しいです。
プログラマのバイブル「達人プログラマー」に「毎年少なくとも一つの言語を学習する」とあります*2。来年選ぶ言語に迷っている方、Kotlinはいかがでしょうか?
Kotlin Advent Calendar 2012(全部俺)はこれでおしまいです。ご覧いただきまして感謝感激雨あられです!
日記
正月休みはKotlin + Vert.xで何かWebサービスつくりたい。
24日目:ツイート検索アプリをつくる
今日はクリスマスイブです。いろんな過ごし方があるかと思いますが、今日はKotlinでアプリを作って遊びましょう。
ツイート検索アプリをつくる
本日のタイトルのとおり、ツイート検索アプリをつくります!と言っても、100行もしない超簡単なアプリです。指定された言葉を含むツイートを検索して、ヒットしたツイートを表示するだけのアプリです。
ライブラリの用意
今回は2種類のライブラリを使います。まずツイートを検索、取得するためのライブラリとして@yusukeさんが開発、提供なさっているTwitter4Jです。そしてもう1つが、Swing*1をKotlinフレンドリにしたライブラリです。
Twitter4Jをこちらのサイトからダウンロードします*2。今回はtwitter4j-3.0.2.zipというファイルをダウンロードしました。twitter4j-core-3.0.2.jarというファイルにパスを通せばTwitter用ライブラリの準備は完了です。
次にKotlin向けSwingライブラリです。これはおそらくKotlin標準ライブラリという位置付けだと思いますが、現時点ではコンパイラと同梱されているライブラリには含まれていません。試したい方はGithubから手に入れてください*3。
ツイート検索をする関数をつくる
まず最初に指定した言葉を含むツイートを検索する関数をつくりましょう。実行するにはTwitter連動アプリとしてTwitterにアプリ登録し、与えられた各種キーを設定ファイルに書き出す必要があります。詳しくはTwitter APIについてぐぐってください。今回はTwitter4Jを使うのでtwitter4j.propertiesというファイルにその情報を記載する方法を採りました。
次にコードを書きます。
private fun searchTweets(keyword: String): List<Status> { val twitter = TwitterFactory().getInstance() val result = twitter?.search(Query(keyword)) return result?.getTweets() ?: Collections.emptyList() }
説明が不要なくらいに簡単な関数になりました。強いて説明するならば、Javaコードにおける参照型はKotlinで扱う際にNULL許容型になるので、演算子 !! や ?: を使用してNULL許容型との折り合いをつけています。
GUI部品をつくる
今回はSwingアプリということでJava標準ライブラリパッケージ javax.swing をKotlinフレンドリにした kotlin.swing というパッケージを使用しました。特徴はコンポーネント等を宣言的に定義できることと、イベント駆動をするようなボタンなどに対してコールバック関数を関数リテラルとして渡すことができるという点です。
全体のソース
ひとつひとつ言葉で説明するよりコードをご覧いただいた方がわかりやすいと思いますので、今回つくったツイート検索アプリの全体のコードを示します。
import java.awt.BorderLayout import java.awt.Font import java.util.Collections import javax.swing.JFrame import javax.swing.JScrollPane import javax.swing.JTextArea import javax.swing.JTextField import javax.swing.SwingUtilities import javax.swing.text.JTextComponent import kotlin.swing.borderPanel import kotlin.swing.button import kotlin.swing.frame import twitter4j.Query import twitter4j.Status import twitter4j.TwitterFactory private fun searchTweets(keyword: String): List<Status> { val twitter = TwitterFactory().getInstance() val result = twitter?.search(Query(keyword)) return result?.getTweets() ?: Collections.emptyList() } /* 拡張関数のように拡張プロパティを定義して * setText, getText をラップする */ private var JTextComponent.text: String get() = getText()!! set (value) = setText(value) private fun createAndShowGUI() { /* JFrameのファクトリ関数。 * 2つ目の引数として初期化用の関数リテラルを渡せる。 */ frame("Tweet Searcher") { setSize(400, 400) setLocationRelativeTo(null) setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) val tweetArea = JTextArea() tweetArea.setLineWrap(true); tweetArea.setEditable(false); tweetArea.setFont(Font(null, Font.PLAIN, 20)); val searchField = JTextField() /* JButtonのファクトリ関数。 * 2つ目の引数としてボタン押下時のコールバック関数を渡せる。 */ val searchButton = button("Search") { val strBuilder = StringBuilder() for(status in searchTweets(searchField.text)) { if(status != null) { strBuilder.append("■") strBuilder.append(status.getText()) strBuilder.append( " [${status.getUser()?.getScreenName()}]") strBuilder.append("\n\n") } } tweetArea.text = strBuilder.toString()!! } add( borderPanel { add( borderPanel { add(searchField, BorderLayout.CENTER) add(searchButton, BorderLayout.EAST) }, BorderLayout.NORTH ) add(JScrollPane(tweetArea), BorderLayout.CENTER) } ) }.setVisible(true) } /* SwingUtilities.invokeLaterへ処理を委譲する。 * 関数リテラルを受け取り、Runnableオブジェクトでラップする。 * invokeLaterをKotlinらしい形に変え、 * 可読性/記述性を高める狙い。 */ private fun invokeSwingApp(invoker : () -> Unit) { SwingUtilities.invokeLater(object : Runnable { override fun run() { invoker() } }); } fun main(args : Array) { invokeSwingApp { createAndShowGUI() } }
実行結果
上記コードをコンパイル、実行すると次のようなウィンドウが表示されます。
テキスト入力欄に検索したい言葉を入力し、隣のSearchボタンをクリックします。すると次のような画面になります。
まとめと次回予告
今日はKotlinからTwitter4JとSwingを使ってツイート検索GUIアプリをつくりました。Java用ライブラリをKotlinから使う際には、NULL許容型と仲良くする必要があります。呼び出すメソッドが null を返し得るのかどうか、よくドキュメントを読んでから 演算子 !! を使用すべきです*4。SwingをKotlinフレンドリにしたパッケージ kotlin.swing に含まれるライブラリは、まだまだ提供されているクラスや関数は少ないですが、便利で面白い機能があります。
明日は待ちに待ったクリスマスです。当アドベントカレンダーも明日で最終回です。
日記
3連休はエンジョイしました。
23日目:JUnitを使う
アドベントカレンダー23日目です。今日はJavaのユニットテスト・フレームワークのデファクト・スタンダードであるJUnitを使って、Kotlinのユニットテストを走らせる方法を紹介します。Kotlinの標準ライブラリに含まれているパッケージ kotlin.test にはJUnitで使える便利な関数群が用意されていますので、それを使ったテストの書き方も併せて紹介します。
JUnitを用意する
まずJUnitを好きな方法で用意してください。そして使える状態にしてください。なお今回は、IntelliJ IDEA上でテストを走らせます。
はじめてのテストコード
KotlinでJUnit用テストコードを書きましょう。
import org.junit.Test as test class Test { test fun myFirstTest() { } }
注目すべき1点目は最初のインポート文です。JUnitの提供するアノテーション Test をインポートして、test という別名を付けています。
次に注目すべき点はテスト対象の関数がクラスのメンバとなっている点です。Kotlinでは名前空間レベルに関数を置くことができるので、わざわざクラスを定義してそのメンバとするのは面倒なので避けたいところです。しかし残念ながら避けることはできません。昨日説明したとおり、名前空間レベルに関数を置くとクラス namespace が自動生成され、そのメンバとなります。実は namespace はコンストラクタをまったく持ちません*1。そのためJUnitがnamespaceをインスタンス化しようとすると例外が投げられてしまいます*2。このような理由でテスト対象の関数は必ずクラス内に置く必要があります。
テストを実行する
さて、テストコードを書いたので実行させてみます。この手順はIntelliJ IDEA固有の話なので、Kotlinとは関係ありませんが、現時点で唯一KotlinをサポートするIDEなので丁寧に紹介したいと思います。
メニューやショートカットキーにより「Run...」を選択します。
すると次のような選択を迫られますので、実行対象を選びます。
テストが実行され、結果が表示されます。
グリーンを得られました!
パッケージ kotlin.test を使ってテストを書く
パッケージ kotlin.test にはJUnit用ユニットテストを書くのに便利な関数がいくつか含まれています。次に示す、引数に与えられた数を +1 した値を返すだけの退屈な関数のテストを書いてみます(すべてのテストをパスします)。
fun next(i : Int = 0) : Int { require(i >= 0) return i + 1 }
import kotlin.test.assertEquals import kotlin.test.expect import kotlin.test.todo import org.junit.Test as test class Test { test(expected = javaClass<IllegalArgumentException>()) fun 引数に負数を渡すと例外が投げられるはず() { next(-1) } test fun 正数を渡すとプラス1された値が返されるはず() { assertEquals(1, next(0)) assertEquals(2, next(1)) assertEquals(3, next(2)) } test fun ゼロから始めて100回繰り返すと結果は100のはず() { expect(100) { (1..100).fold(0) { (a : Int, e : Int) -> next(a) } } } test fun おまけ() { todo { // ここはテスト結果には影響しないんだぜ throw Exception() } } }
まだまだ提供されている関数は少ないですが、Kotlinの特性(特に関数リテラル)を活かした関数があるので使ってみると面白いかも知れません。
まとめと次回予告
今日はKotlinコードをJUnitでテストする方法を学びました。パッケージ kotlin.test が提供する関数を使ったテストコードの簡単な例を見ました。このパッケージは発展途上なので今後の展開に期待です。
明日はKotlinコードからサードパーティ製のJavaライブラリやフレームワークを使う例をご覧に入れたいと思います。
日記
昨日は会社の友達と飲みました。
22日目:Javaとの相互運用
3連休初日キタ━(・∀・)━!!!
アドベントカレンダー初日にお話ししましたが、KotlinはJVM言語なのでコンパイラが吐くのはJavaバイトコードです。それらはJava言語から使用することができます。またその逆に、Java言語で書かれたクラスをKotlinから使用することもできます。今日はKotlinとJavaの相互運用についてお話しします。
KotlinからJavaコードを使う
Java→Kotlin
同じオブジェクト指向言語として、やはりJavaとKotlinには類似点が多くあります。例えばJavaの void はKotlinの Unit にそのまま対応しています。このような対応関係をまずは把握しましょう。
Javaコード | Kotlinではこうなる |
---|---|
返値型がvoidのメソッド | 返値型がUnitの関数 |
Kotlinの予約語と同一の識別子 | 使用する際はバッククオートで括る(`is`など) |
参照型(例えばInteger) | NULL許容型(例えばInt?) |
チェック例外 | 非チェック例外(捕捉の義務はない) |
List<? extends Hoge> List<? super Hoge> |
型プロジェクション(使用箇所宣言) List<out Hoge> List<in Hoge> |
raw型(原型) List |
スタープロジェクション List<*> つまり List<out Any?> |
java.lang.Object | Any? |
java.lang.Objectが提供するメソッド群
Kotlinでは、すべてのクラスのスーパクラスは Any です。上記の表のようにJavaコードにおけるObject型はKotlinで扱う際にAny?型に変換されます。しかしクラス Any にはいかなるメンバも定義されていません。それでは Object の提供するメソッドはどのようにKotlin側で扱えばよいのでしょうか。
メソッド toString とequals、finalize、clone()は、プログラマ視点では特に違いはありません。Kotlinがどのような仕掛けでそれらのメソッドを呼び出しているか気になる方は、Kotlinのソースコードを読んでみてください。メソッド hashCode、wait、notify、getClassは、Javaコードで定義されたクラスのみが持ちます。wait、notifyがKotlinで廃止されていることはいいことです*1。
JavaからKotlinコードを使う
名前空間レベルの関数
KotlinではJavaと異なり名前空間レベルに関数を置くことができます。実はコンパイルすると、namespaceという名前のクラスが自動生成され、そのクラスが持つ staticメソッドとなります。
例外
Kotlinにはチェック例外はありません。すべて非チェック例外です。これは単にjava.lang.RuntimeExceptionかそのサブクラスであるということではなく、文字通り非チェック例外なのです。
KotlinではJavaと同じように例外を投げることができますが、関数にスロー宣言はありません。例えば次のようなKotlinコードがあるとします。
fun throwIOException() { throw java.io.IOException() } fun main(args : Array<String>) { try { throwIOException() } catch(e : java.io.IOException) { e.printStackTrace() } }
このコードはコンパイル可能です。実行するとスタックトレースが表示されるだけのプログラムです。
上記の関数 throwIOException をJavaコードから呼び出して、上記の main と同じようなコードを書いてみます。
// Java public class Sample { public static void main(String[] args) { try { namespace.throwIOException(); } catch(java.io.IOException e) { e.printStackTrace(); } } }
「名前空間レベルの関数」で説明したように、関数 throwIOException はクラス namespace 内のstatic関数となるので、同じパッケージ内にいれば例のJavaコードのように呼び出すことができます。しかし、このコードはコンパイルに失敗します。問題の箇所は、catch節です。try節からIOExceptionはスローされ得ないという解釈がされるからです。繰り返しになりますが、Kotlinの関数はスロー宣言ができないので、このようなことが起こるのです。
この問題に対してKotlinプロジェクトチームは次のような解決策を挙げています。
- スロー宣言だけで、何も仕事をしないメソッドを定義し、それとセットで使う
- Throwableで補足してinstanceofチェックをする。あまりエレガントじゃない
- Javaでラッパメソッドを書く
- Kotlin側の関数に、アノテーションthrowsを用いてスロー宣言を行う(現時点では実装されてなさそう...)
プロパティ
プロパティには、setterとgetterを使ってアクセスします。
class Person() { var name : String = "" }
// Java final Person taro = new Person(); taro.setName("Taro"); System.out.println(taro.getName()); // Taro
まとめと次回予告
今日はKotlinとJavaとの相互運用についてお話ししました。両者の違いと、その差異を吸収する仕組みを知ることが相互運用の場面では重要になると思います。もっとも、KotlinコードをJavaから使用することはあまりないかも知れませんが。
明日はJavaのユニットテスト・フレームワークとして名高いJUnitと、Kotlinの標準ライブラリを使ったユニットテストの書き方を紹介します。
日記
東京では、クリスマスイブに雪が降るらしいですね。
21日目:その他の機能いろいろ
今日でKotlin言語の文法や機能の説明は最後です。今日ご紹介するのは、これまでに紹介していない残りの機能についてです。中にはまだ実装されていない(正しくコンパイルされない)機能もありますが、それらは今後のマイルストーンで実装されていくでしょう。では、始めます。
キャスト
キャスト、それは型の強制変換術です。時としてそれは、プログラムにスタックトレースを吐かせ死に追いやる恐ろしい術です。NULL安全機構を導入している心配性のKotlinは、このキャストとどうやって付き合うのでしょうか。
型チェック
まずは道具を準備しましょう。いわゆる is-a 関係(つまり継承関係)をチェックすることにより、むりやり型を合わせようとして失敗することを未然に防げます。Javaではキーワード instanceof を使用しましたが、Kotlinではより簡潔で直感的なキーワード is を使用します。その否定形は !is です。is(あるいは !is)によって評価された式は Boolean値を返すので、if式やwhen式などで使用できます。次のコードはwhen式のパターンマッチによる型チェックの例です。
val hoge : Any = "kotlin is fun!" val result = when(hoge) { is Int -> "im Int" is Char -> "im Char" !is String -> "im not String" is String -> "im String" else -> throw AssertionError() } println(result) // im String
スマートキャスト
ではキャストの方法について紹介していきます。いくつかやり方があるうちの1つ目がスマートキャストです。これは。型チェックを済ませたあとのブロックでは、その型に自動的にキャストされる仕組みです。具体的には次のコードを見てください。
val hoge : Any = "kotlin is fun!" if(hoge is String) { println(hoge.toUpperCase()) // KOTLIN IS FUN! }
変数 hoge が型 String であるかif によりチェックしており、そのブロック内で hoge が String のように振る舞うことができます。Javaでは型チェックをした後、明示的にキャストをしないとならない場面ですが、その手間をKotlinでは省けます。
演算子 as による安全でないキャスト
次に紹介するのは演算子 as を使用した、キャストっぽいキャストです。この方法は、演算子 as に続けて変換したい型を指定します。ただし、キャストに失敗した場合には実行時例外を投げますのでご注意ください。
println*1.toUpperCase()) // KOTLIN IS FUN!
演算子 as? による安全なキャスト
慎重なKotlinには、やはり安全なキャストが用意されています。演算子 as? を、先に説明した as のように使用するやり方です。しかしこの場合は、キャストに失敗したときには例外はスローされず null が返されます。
val hoge : Any = "kotlin is fun!" val i : Int? = hoge as? Int println(i) // null
これは面白い機能です。NULL安全により、次のようなキャストからの関数呼び出しも安全に失敗してくれます。
val piyo : Any = 123 println*2 // null
クラスオブジェクトとジェネリクス制約
まず最初に言いますが、この機能は現時点では実装されていません。こちらの issue に対応しています。
さて、いきなりですが次のクラスオブジェクトとトレイトを組み合わせたファクトリ関数の例を見てください。
trait Factory { fun create() : T } class Coffee private() { class object : Factory { override fun create() = Coffee() } }
これは昨日の例でも見たクラスオブジェクトとトレイトの上手い使い方の典型です。コーヒーにラベルを貼る必要が出たとして、便宜的に次のような関数を用意します。
fun getLabeledCoffee(label : String, coffee : Coffee?) = Pair(label, coffee ?: Coffee.create())
引数にラベルを表す文字列とコーヒーを取ります。ラベルとコーヒーのペアを生成して返しますが、引数のコーヒーに null が渡された場合には、ファクトリ関数を使ってコーヒーを作成します。
将来的に緑茶などにも対応する必要があるので、この関数を汎用的に改造します。直感次のようなコードを書きました。
fun <T> getLabeledObject(label : String, t : T?) = Pair(label, t ?: T.create())
これは文法的に正しくありません。型引数 T に対して関数 create を呼び出そうとしているのがおかしいです。Tにはどのような型が来るのかわからないからです。
では、型引数 Tに対して「トレイト Factory をミックスインしたクラスオブジェクトを持つ型である」という制約を設定しましょう。これは17日目に解説した型引数に上限制約を設ける話に似ていますが、同じでないことに注意してください*3。
fun getLabeledObject(label : String, t : T?) where class object T : Factory = Pair(label, t ?: T.create())
上記のようにclass object 型引数 : トレイトを where節に記述すると、上手く行きます。
例外
基本的にKotlinの例外機構はJavaと同じです。例外クラスはクラス Exception のサブクラスです。文法的な話をするならば、やはり try-catch-finally はありますが、これらは式なので値を返します。例外をスローしたいときはキーワード throw を使用します。
Javaとの違いは、チェック例外がなく、すべて非チェック例外であることです。チェック例外はプログラマの作業を増やしAPIを使い難いものにしたり、作業を強制されるのでcatch節でつい握り潰してしまったりと、あまりいいことがないからです*4。
列挙型
列挙型は、現在開発中の機能であることに注意してください。
Kotlinには列挙型(enum)があります。Javaのそれと同じような使い方ができます。
コンストラクタに値を渡す場合は次のように記述します。
enum class Currency(val country : String) { JPY : Currency("Japan") USD : Currency("USA") GBP : Currency("UK") }
さらに、列挙型定数(例えば上記の JPYなど)は匿名サブクラスのインスタンスとなることもできます。
import java.math.BigDecimal import kotlin.math.times enum class Currency(val country : String) { JPY : Currency("Japan") { override fun toJPY(money : BigDecimal) = money } USD : Currency("USA") { override fun toJPY(money : BigDecimal) = money * BigDecimal(84.19) } abstract fun toJPY(money : BigDecimal) : BigDecimal }
Kotlinの列挙型は機能が豊富です。クラスのように open 宣言することで、他の列挙型から継承できるように設定できます。また、トレイトをミックスインすることも可能です。
まとめと次回予告
今日はいろいろ学びました。キャスト、クラスオブジェクトのジェネリクス制約、例外、列挙型についてです。キャストには危険なキャストと安全なキャストがありました。クラスオブジェクトのジェネリクス制約はまだ実装されていませんが非常に便利な機能です。例外、列挙型はJavaのそれらと似ていますが、改善されています。
明日は...なに書こうw
日記
早く3連休来い!
20日目:データクラスと多重宣言
アドベントカレンダー20日目です。アドベントカレンダーも残すところあとわずかです。言語の文法や機能の紹介は明日まで、という風な予定となっています。今日はデータクラスと多重宣言という機能について見て行きます。
データクラス
単純なプロパティしか持たないようなクラス、いわゆるデータクラスを定義するのにKotlinは持ってこいの文法を採用しています。と言うのは、例えば名前、価格というデータしか持たないクラス Product の定義は次のように簡潔に表現できるからです。
class Product(val name : String, val price : Int)
これは見た目もシンプルですし、記述量も少なくて素晴らしいです。しかしデータクラスたるもの、等価であるか比較したり(関数 equals )、文字列表現を取得したい場合(関数 toString )があります。それらはデータクラスにおいて、たいていの場合同じ内容になります。
例えば上記のクラス Product の場合、name、price これら両方の値が等しい Product インスタンス同士は等しいと見なします。また、関数 toString によって取得される文字列は Product が含むすべてのプロパティの値を上手いこと含んでいれば十分です*1。
ここでアノテーション data の登場です!データクラスにアノテーション data を付けると、equals や toString といった関数を自動生成してくれます。
data class Product(val name : String, val price : Int) fun main(args : Array<String>) { println(Product("beer", 200) == Product("beer", 200)) // true println(Product("sake", 600)) // Product(name=sake, price=600) }
さらにアノテーションdata が付いたクラスには、次に説明する関数 component* も 自動生成されます。
多重宣言
多重宣言は、例えば次のようなことができます。
val (foo, bar) = Product("beer", 200)
これは他の言語の多重代入に似ています(Javaにはこのような機能はありませんが)。お察しの通り、変数 foo には "beer" が、変数 bar には 200 が代入されます。
どのような仕組みで多重宣言が行われているのでしょうか。実は先に少し触れた、関数 component* が重要な鍵を握っています。
ひとまずクラス Product からアノテーションdata を外します。すると上記のコードはコンパイルエラーになりますが、Product を次のように書き直すと、再びコードはコンパイルされ、実行結果も期待通りのものとなります。
class Product(val name : String, val price : Int) { fun component1() = name fun component2() = price }
コードを見るとわかるとおり、関数 component1 の返り値が変数 foo に、関数 component2 の返り値が変数 bar に代入されます。
component3 以降も定義されすれば期待通りに動いてくれます。これが多重宣言です。
ペアとマップ
クラス Pair は、2つの任意の値をまとめるためのコンテナです*2。次のように、そのインスタンスを得ることができます。
val one = Pair("one", 1) val two = "two" to 2
上の例はコンストラクタからインスタンスを生成しています。下の例は関数から Pair インスタンスを生成していますが、関数名からわかるとおり、この方法はキーバリュー値として Pair を用いたい場合に使う方がよさそうです。
クラス Pair ではメンバ関数として component1 と component2 を定義しています。つまり、多重宣言が可能です。
val (text, number) = Pair("three", 3)
ところで、Kotlinの標準ライブラリのパッケージ kotlin では、jet.Map の拡張関数として iterator を提供しています。このイテレータの要素の型は Map.Entry です。そして、Entry の拡張関数として component1 と component2 が定義されています。
つまりマップのイテレート + 多重宣言で次のようなスッキリとしたコードを書くことが可能になります。
val map = hashMap( "one" to 1, "two" to 2, "three" to 3 ) for((key, value) in map) { println("$key -> $value") }
まとめと次回予告
今日はデータクラスと多重宣言という面白い機能についてお話ししました。アノテーション data をクラスに付けることによって、データクラスのための関数が自動生成されます。また関数 component* を適切に定義すると、そのクラスのインスタンスから多重宣言により複数の変数に一度に値を代入することができます。
明日のトピックは...明日になってからのお楽しみ!次回もサービスサービスぅ!!
日記
今朝、めちゃくちゃ寒い(´・ω・`)
19日目:キーワード object の威力
今日はキーワード objectの機能について紹介します。この object というキーワードは3つの機能を提供します。オブジェクト式、オブジェクト宣言、クラスオブジェクトの3つです。これらの機能は名前も使用するキーワードも似ているので、混同しないよう気をつけて学んでいきましょう。
オブジェクト式
匿名クラスというものがあります。既存のクラスのサブクラスを使用箇所で定義し、そのままインスタンス化するという手法です。Kotlin でもできます。Kotlinではインスタンス生成に new を使用しないということを念頭に置くと直感的に次のようなコードを思い浮かべるかと思います。
// Foo はクラス val foo = Foo() { }
しかし、この記述では、コンストラクタの最後の引数に関数リテラルを渡しているとコンパイラは解釈します。そこで匿名クラスのためのキーワードが用意されています。それが object です。クラス Foo の匿名サブクラスのインスタンスを返す式を次のように記述することができます。
object : Foo() { }
サブクラスなのでスーパクラスの関数を(許可されていれば)オーバライドすることはもちろん、新たにメンバを定義することも可能です。また、クラス Any の匿名サブクラスのインスタンスが欲しい場合は、スーパクラスを指定しない次の記述ができます。
object { }
「オブジェクトを返す式」という意味で、この機能をオブジェクト式と言います。
オブジェクト宣言
Kotlinでは言語の機能としてシングルトンをサポートしています。シングルトンとしたいクラスは、キーワード class の代わりに object を使用します。このようにキーワード object によってシングルトンクラスを宣言する機能をオブジェクト宣言と言います。
trait Greeter { fun greet() } object GreeterImpl : Greeter { override fun greet() { println("Hello") } } fun main(args : Array<String>) { GreeterImpl.greet() // Hello val greeter : Greeter = GreeterImpl greeter.greet() // Hello }
オブジェクト宣言したクラスでは、プライマリ・コンストラクタを定義できないことに注意してください。
クラスオブジェクト
Javaでは、クラスは staticメンバ(クラスメンバ)を持つことができます。しかしKotlinには static というキーワードはありません。その代わり、関数や変数をクラスに属すことなく名前空間レベルに置くことができます。ほとんどの場合、これで問題はありませんが、困ることもあります。クラス内部の privateメンバにアクセスする場合です。よくあるのがコンストラクタを private にして、代わりに staticファクトリメソッドを提供するという手法です*1。
Javaで言う staticメンバをKotlinではクラスオブジェクトによって実現しています。クラスオブジェクトはクラス内に定義されるので、クラス内のprivateなメンバにアクセスできます。
クラスオブジェクトはキーワード class object によって宣言します。そこから波括弧によってブロックを形成します。その中に、関数や変数を定義していきます。
class Foo private() { class object { fun create() = Foo() } }
上記の例ではクラス Foo の中にクラスオブジェクトとして関数 create を定義しました。クラス Foo のプライマリ・コンストラクタは private として宣言されており、Foo の外側ではこのコンストラクタを呼び出せません。クラス Foo のコンストラクタは隠蔽されており、そのインスタンスはクラスオブジェクトの関数 create により提供される、と言い換えることができます。次に関数 create を呼び出す例を示します。
val foo = Foo.create()
クラス、ドット、クラスオブジェクトといった具合にアクセスすることができます。これはJavaのstaticメンバと同じですね。
クラスオブジェクトは、staticメンバと違いインスタンスメンバとソースコード上、混同しないという可読性の点でのメリットがあります。また、クラスオブジェクトはトレイトをミックスインすることができます。
trait Factory<T> { fun create() : T } class Foo private() { class object : Factory<Foo> { override fun create() = Foo() } } fun main(args : Array) { val factory : Factory<Foo> = Foo val foo = factory.create() }
まとめと次回予告
今日はキーワード object を用いる3つの機能について学びました。まず1つ目がオブジェクト式でした。これは匿名サブクラスの定義からインスタンス化までを行うための機能です。次にオブジェクト宣言を学びました。これはクラスをシングルトンにする機能です。最後にクラスオブジェクトについて学びました。クラス内に属してインスタンスに属さない関数や変数の定義方法を知ることができました。
そろそろアドベントカレンダーも終わりに近づいています。明日、明後日はまだ見ていないKotlinの言語機能について見て行きましょう。
日記
今週末の飲み会のための店をまだ押さえていない。ま、なんとかなるか。