プロジェクト

全般

プロフィール

Kotlinいろいろメモ

Kotlinでプログラミング事始め

Kotlinでプログラミングを始めるには? をメモ

用意するもの

Kotlin言語の開発元であるJetBrains社のIDE(統合開発環境)であるIntelliJ IDEAが最も苦労なくKotlinの開発ができます。EclipseやNetBeans IDEにもKotlinプラグインは存在するようですが、設定に苦労するだけでなく、安定性に欠けるので、IntelliJ IDEA(もしくはAndroid Studio)一択です。IDEを使わないのは、まともな武具を持たずに戦に出るようなもの…。

  • Java SE Development Kit
  • IntelliJ IDEA Community Edition

ソースファイル

Kotlinは、Javaと違って必ずしもソースファイル名とクラス名を一致させなくても、パッケージとディレクトリを一致させなくても、複数のクラスを1つのファイルにまとめても構いません。しかし、Javaに合わせておくと混乱が少ないので、基本的にはJavaのやり方に合わせます。

クラスとクラスに関係するトップレベル関数や拡張関数を一つのファイルに記述し、ファイル名はクラス名に合わせます。
パッケージ名に合わせたディレクトリ階層にソースファイルを配置します。

クラスの定義

定義しようとするクラスの目的・用途を確認します。

クラスの種類

  • データクラス(値クラス)かどうか
    コレクションに詰めることがあるクラス、インスタンスを比較するときにインスタンスそのものの一致ではなく、インスタンスの持ついくつかの値の一致で判断するクラスはデータクラス(data class)が適しています。
  • シングルトンかどうか
    定義したクラスのインスタンスは1つだけしか存在しない場合(シングルトンパターンは厳密にはインスタンスが1つだけではなくインスタンスの数を制限するものですが)は、オブジェクトクラス(object)が適しています。
  • 列挙型(enum)かどうか
  • 継承を許すかどうか
    デフォルトは継承を許さないので、許す場合はopen修飾子を付けます。

クラスの公開・継承可否

クラスのデフォルトのアクセス修飾子はpublicです。パッケージ外に公開しない場合は、internalもしくはprivateとします。

Kotlinのアクセス修飾 意味 Javaでは
public 他のパッケージに公開。Kotlinはアクセス修飾子を省略するとこれになる public
internal 一緒にコンパイルするファイルに公開。 Javaに該当するアクセス修飾なし
protected サブクラスに公開。同一パッケージには非公開。 Javaのprotectedに近いが、パッケージ内公開可否の点が違う
private クラス内にのみ公開 private

クラスのデフォルトの継承可否は否(Javaでのfinalクラス)です。
openを付けると、継承可となります。

main関数

プログラムを実行するときのエントリポイントとなるmain関数を定義します。Javaではクラスに静的メソッドmainを定義していましたが、Kotlinではトップレベル関数mainを定義します。
コマンドライン引数は文字列の配列として関数の引数に取り、関数の型はUnit(Javaでのvoid)となります。

  • MyApplication.kt
    fun main(args: Array<String>) {
        if (args.size < 1) {
            println("引数を1つ指定しなくてはなりません")
            exitProcess(1)
        }
        // ...
    }
    
  • String型のインスタンスの配列は、Array<String>
  • 配列はpublicなメンバー(フィールド)sizeで要素数を参照
  • プロセスを終了させるときは、kotlin.system.exitProcess関数推奨(JavaのSystem.exit()も使える)

言語仕様

クラスを定義する

プライマリコンストラクタ

  • class宣言と一緒にコンストラクタ引数を指定する
    • コンストラクタ引数は、初期化ブロック内で参照可能
  • コンストラクタ引数宣言のみでコードは記述できない
    • 初期化ブロックにコードを記述
  • コンストラクタ引数にvalまたはvarを付けると、クラスのプロパティとして定義される

次は同じクラスが定義されます

class User(val nickname: String)
class User constructor(_nickname: String) {
    val nickname: String
    init {
        nickname = _nickname
    }
}
class User(_nickname: String) {
    val nickname = _nickname
}

データクラス

hashCode、equals、toStringのメソッドをオーバーライドしたクラスを定義します。

data class Member(val name: String, val id: Int)

equalsの比較はプライマリコンストラクタで宣言されたすべてのプロパティの値が等しいことをチェックします。
hashCodeはプライマリコンストラクタで宣言されたすべてのプロパティのハッシュ値に依存した値を返却します。

また、copyメソッドも自動で生成されます。

関数を定義する

基本的な関数の定義は次の書式です(型パラメータを使う場合は別途)。

fun 関数名(引数1の名: 引数1の型, 引数2の名: 引数2の型): 戻り値の型 {
    関数の本体
}

null安全プログラミング

null検査とスマートキャスト

ミュータブルなプロパティ

varプロパティのnull検査はスマートキャストが働きません。

class FooAdapter(private var cursor: Cursor?) {
    fun getItemCount(): Int {
        // 処理
    }
}

FooAdapterのプロパティ cursor は、データが更新されると差し替えるため可変(var)であり、また nullable です。
まず最初に想定した処理は次です。これはコンパイルエラーとなります。

return if (cursor != null && !cursor.isClosed()) cursor.count else 0

エラー内容は次です。

Smart cast to 'Cursor' is impossible, because 'cursor' is a mutable property that could have been changed by this time

cursor は var宣言されたミュータブルなプロパティのため、nullチェックをしても別スレッドで変更される可能性があり、nullableでない型にスマートキャストできないということです。

ここでは、特定のスレッド(例、UIスレッド)のみがこの処理を実行するため別スレッドによる変更は設計上ないものとします。

修正案は次があります。

  1. 非null表明
  2. ローカル変数に載せ替え
  3. let関数とエルビス演算子

(1) 非null表明による修正コードは次です。

return if (cursor != null && !cursor!!.isClosed()) cursor!!.count else 0

非null表明は危険を承知で突っ込むぞ!! 感が満載です。
なお、この例のように1つの行に複数の非null表明を書くのはダメです(NullPointerExceptionは行番号を示すのでどちらの表明が例外となったか判別できない)。

(2) ローカル変数に載せ替えてみます。

val lc = cursor
return if (lc != null && !lc.isClosed()) lc.count else 0

載せ替えコードが目立ちます。釈然としない感があります。

(3) let関数を使用します。

return cursor?.let { if (it.isClosed()) 0 else it.count } ?: 0

cursor?.let でcursorがnullでない時の処理をラムダ式で指定します。
letの引数のラムダ式では、itでレシーバを参照します。
また、エルビス演算子で cursorがnullだった場合の処理を記述しています。

バイトコード

Kotlinソースファイルからコンパイルされたバイトコードに関するメモ

トップレベル関数とクラス

1つのソースファイル(Hello.kt)に、クラスHelloとトップレベル関数sayHelloを定義したとします。

class Hello (val message: String)

fun main(args: Array<String>) {
    println("Hello")
}

これをKotlinコンパイラでコンパイルすると、2つのクラスファイル Hello.class, HelloKt.class が生成されます。
Hello.classには、クラスHelloの定義が書かれ、HelloKt.classには、クラスHelloKtとその中にstaticメソッドmainの定義が書かれています。