Kotlin Advent Calendar 2012 (全部俺)

JavaプログラマのためのKotlin入門

25日目:まとめとこれから

f:id:ngsw_taro:20121225092935j:plain

メリークリスマス!!!!

今日はクリスマス!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 のリリースに向けて、それらは日々進化しています。その他にはモジュールシステム*1Eclipse用プラグインの登場が待たれます。

全体のまとめ

Kotlinの概要紹介から始まり、基本構文、関数型プログラミング的アプローチ、オブジェクト指向的アプローチ、ライブラリ/フレームワークとの連携例を紹介してきました。これらを通して改めて感じることは、Kotlinは

  • 少ない記述量で多くを表現できる
  • より安全な方向へプログラマを導く

言語であるということです。これら2つの特長は、ソフトウェアが複雑になればなるほど、その効果が顕著に現れることでしょう。それでいて敷居の高さを感じさせない文法やルールを採っています。Kotlinに可能性を感じずにはいられません。

おわりに

一人アドベントカレンダーは初めての挑戦な上に、やろうと思ったのが11月最後の週だったので書き溜めはおろか、構想も十分に練っていない状態で始めました。なんとか毎日記事を投稿できて安心しています。みなさんにちょっとでもKotlinの面白さ、かわいさが伝われば嬉しいです。

プログラマのバイブル「達人プログラマー」に「毎年少なくとも一つの言語を学習する」とあります*2。来年選ぶ言語に迷っている方、Kotlinはいかがでしょうか?

Kotlin Advent Calendar 2012(全部俺)はこれでおしまいです。ご覧いただきまして感謝感激雨あられです!

日記

正月休みはKotlin + Vert.xで何かWebサービスつくりたい。

*1:Kotlinコードでビルドスクリプトのような物を記述できるKotlin用のツール(?)です。現在、再設計中らしいです。

*2:14ページ

24日目:ツイート検索アプリをつくる

f:id:ngsw_taro:20121222110447j:plain

今日はクリスマスイブです。いろんな過ごし方があるかと思いますが、今日は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()
  }
}

実行結果

上記コードをコンパイル、実行すると次のようなウィンドウが表示されます。

f:id:ngsw_taro:20121224211354p:plain

テキスト入力欄に検索したい言葉を入力し、隣のSearchボタンをクリックします。すると次のような画面になります。

f:id:ngsw_taro:20121224211456p:plain

まとめと次回予告

今日はKotlinからTwitter4JとSwingを使ってツイート検索GUIアプリをつくりました。Java用ライブラリをKotlinから使う際には、NULL許容型と仲良くする必要があります。呼び出すメソッドが null を返し得るのかどうか、よくドキュメントを読んでから 演算子 !! を使用すべきです*4。SwingをKotlinフレンドリにしたパッケージ kotlin.swing に含まれるライブラリは、まだまだ提供されているクラスや関数は少ないですが、便利で面白い機能があります。

明日は待ちに待ったクリスマスです。当アドベントカレンダーも明日で最終回です。

日記

3連休はエンジョイしました。

*1:Java標準ライブラリに含まれているGUIツールキットです。

*2:Mavenを使う場合は簡単に利用できます。詳細はTwitter4Jのサイトをご覧ください。

*3:私が試した時点では、このライブラリ内にいくつかの構文エラーがあったので注意してください。

*4:KotlinでNullPointerExceptionを投げる危険性を持つ演算子。詳細は16日目の記事を参照してください。

23日目:JUnitを使う

f:id:ngsw_taro:20121221223856j:plain

アドベントカレンダー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...」を選択します。

f:id:ngsw_taro:20121221232600p:plain

すると次のような選択を迫られますので、実行対象を選びます。

f:id:ngsw_taro:20121221232825p:plain

テストが実行され、結果が表示されます。

f:id:ngsw_taro:20121221232853p:plain

グリーンを得られました!

パッケージ 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ライブラリやフレームワークを使う例をご覧に入れたいと思います。

日記

昨日は会社の友達と飲みました。

*1:文字通りコンストラクタを持ちません。生成されたクラスファイルを javap コマンドで読んでみてください。

*2:ちょっと自信がありません。現在、調査中。

22日目:Javaとの相互運用

f:id:ngsw_taro:20071123143347j:plain

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の標準ライブラリを使ったユニットテストの書き方を紹介します。

日記

東京では、クリスマスイブに雪が降るらしいですね。

*1:Effective Java 第2版の「項目69 waitとnotifyよりコンカレンシーユーティリティを選ぶ」を参照してください。

21日目:その他の機能いろいろ

f:id:ngsw_taro:20121220192158j:plain

今日で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 {
  JPY
  USD
  GBP
}

コンストラクタに値を渡す場合は次のように記述します。

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連休来い!

*1:hoge as String

*2:piyo as? String)?.toUpperCase(

*3:単なる型引数に対する上限制約では、上記の例においては t.create() はできるようになりますが、T.create()はできません。

*4:Effective Java 第2版「項目59 チェックされる例外を不必要に使用するのを避ける」「項目65 例外を無視しない」を参照してください。

20日目:データクラスと多重宣言

f:id:ngsw_taro:20050408132103j:plain

アドベントカレンダー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* を適切に定義すると、そのクラスのインスタンスから多重宣言により複数の変数に一度に値を代入することができます。

明日のトピックは...明日になってからのお楽しみ!次回もサービスサービスぅ!!

日記

今朝、めちゃくちゃ寒い(´・ω・`)

*1:関数 toString によって得られる文字列は、デバッグやログ出力のみに使われるべきです。少なくとも、その文字列に依存した挙動を行う処理は避けるべきです。

*2:余談ですが、Kotlinの旧バージョンでは、ペアやトリプルなどはタプルという機能で表現されていました。

19日目:キーワード object の威力

f:id:ngsw_taro:20051017000521j:plain

今日はキーワード 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の言語機能について見て行きましょう。

日記

今週末の飲み会のための店をまだ押さえていない。ま、なんとかなるか。

*1:Effective Java 第2版「項目1 コンストラクタの代わりにstatic ファクトリーメソッドを検討する」を参照してください。