Open9

Kotlin学習 その1

Kohne62Kohne62

Kotlinとは

  • 静的型付けのオブジェクト指向言語
  • JVM(Java Virtial Machine)で動作する
  • Androidアプリ開発、サーバーサイド開発
  • JavaScriptへのトランスパイルも可能
  • 高階関数をサポートしており、関数オブジェクトを引数にしたり、戻り値にしたりできる
  • ラムダ式も使える
  • Kotlin開発元であるJetBrains社がIntelliJ IDEAというIDEを開発している(公式サポートが強い)
Kohne62Kohne62

~ Javaと比較して ~

fun main() {
    println("Hello, world!!!")
}
  • トップレベルに関数を定義することができる
  • 関数定義は funで始まる
  • Kotlinでは意味のある戻り値がない関数やメソッド(Javaでいうvoid型のメソッド)の戻り値の方はUnit型と定義され、記述を省略することができる
Kohne62Kohne62

Gradleについて

  • GroovyというJVM言語を使って処理を行うビルドツール
  • Kotlinではこれを改良し「Gradle Kotlin DSL」というソフトウェアを開発し、ビルド用のスクリプトも全てKotlinで書くようになっている
  • Kotlin開発のプロジェクト上にあるbuild.gradle.ktsがビルド用のスクリプト
Kohne62Kohne62

基本文法

  • 変数の宣言にはvar, valを使う
    • varは再代入可能、valは不可能
    • 型推論してくれるので型の記述は省略可能
  • 条件判定にはwhen文
val value = 1
val str = when(value) {
    1-> "one"
    2-> "two"
    else -> "other"
}
  • 左側を条件式にすることも可能
val value = 1
when {
    value == 1-> println("one")
    value == 2-> println("two")
    else -> println("other")
}
  • NULL安全
    • Kotlinにおける通常の型はnullの代入を許容しない
var a: String = "abc"
a = null // => コンパイルエラー
  • nullを代入するには型の後ろに?をつけて定義する
var b: String? = "abc"
b = null // => OK

Null許容型のメソッドやプロパティを参照するとコンパイルエラーとなる。

  • 参照するためには事前にifを用いてnullが出ないことをチェックする必要がある。
var b: String? = "abc"
val l : Int = b.length // => コンパイルエラー

var b: String? = "abc"
val l : Int = if(b != null)b.length else -1

// ?演算子を使えばNull許容型のメソッドやプロパティを参照することができる。
// ただし、lの型はInt?となるためNull許容型であることに注意
val l : Int? = b?.length
  • ifによるnullチェックは?:演算子を用いて記述することができる
    エルビス演算子
// bがnullの場合は-1となり、lの型はNull不許容型であるIntになる
val l : Int = b?.length ?: -1
  • !!演算子
    • 基本的に非推奨
    • Null許容型をNull不許容型に無理やり変換する。
val : Int = b!!.length

// この場合lはInt型となるが、bがNullの場合は実行時にNullPointerExceptionが発生してしまう。

どうしてもNull不許容型に変換しなくてはならなくなったら標準関数のrequireNotNullを使うことを検討する

Kohne62Kohne62

関数

  • 名前付き引数は引数の順番を変えても動く。名前付き引数を使った場合、後に続く引数には全て名前付き引数を入れなければならない。(引数の順番が合っていれば省略可?)
fun toAddFormula(value: Int, num: Int = 1): String {
    return "$value + $num"
}

val result1 = toAddFormula(num = 3, value = 1234) // "1234 + 3"
val result2 = toAddFormula(num = 4, 101) // コンパイルエラー
val result3 = toAddFormula(value = 12, 7) // "12 + 7"
  • 型パラメータを使うこともできる
fun <T> singletonList(item: T): List<T> {
}
Kohne62Kohne62

高階関数とラムダ式

  • 高階関数: 関数オブジェクトを引数にしたり、戻り値にする関数
fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}
  • ラムダ式: 関数を宣言せずに関数オブジェクトをすぐに生成することができる
max(strings, { a, b -> a.length < b.length })

// ↓関数の引数の最後にラムダ式を渡している場合は引数の外に出して記述することができる
max(strings) { a, b -> a.length < b.length }
Kohne62Kohne62

インライン関数

  • 実行時に関数オブジェクトを生成するとメモリアロケーション等コストが高くなる場合がある。これを解決するためにインライン関数を使用する。

インライン展開(インラインてんかい、英: inline expansion または 英: inlining)とは、コンパイラによる最適化手法の1つで、関数を呼び出す側に呼び出される関数のコードを展開し、関数への制御転送をしないようにする手法。これにより関数呼び出しに伴うオーバーヘッドを削減する。

inline fun <T> lock(locl: Lock, body: () -> T): T {
   // ...
}
Kohne62Kohne62

クラス

  • インスタンスを生成する際、newキーワードは不要
class MyClass {
  // ...
}
val myClass = MyClass()
  • 明示的にコンストラクターを定義する場合、クラス名の後に定義する。これをプライマリーコンストラクターという。
class Person(name: String) {
}

// アノテーションや可視性修飾子をつける場合はconstructorキーワードが必要になる
class Person public @Inject constructor(name: String) {
}
  • プライマリーコンストラクターには処理を記述することはできない。インスタンス生成時に行いたい初期化処理はイニシャライザー(init)に記述する。
class Person(name: String) {
    init {
        logger.info("name = ${name}")
    }
}
  • コンストラクターを複数定義する場合、セカンダリーコンストラクターを定義する必要がある。
class Person(val name: String) {
    // thisを使ってプライマリーコンストラクタを呼び出している
    constructor(name: String, parent: Person) : this(name) {
        // 処理
    }
}
Kohne62Kohne62

プロパティ

  • 上記のプライマリーコンストラクターに付いていたvalはプロパティであり、変更不可能な読み取り専用プロパティになることを定義している。(varは変更可能プロパティ)
class Person {
    var name: String = ""
}

// 上の記述をJavaで書いた場合
final class Person {
    private String name;
    public String getName() {
        return name;
    }
    public String setName(String name) {
        this.name = name;
    }
}
  • 自分でgetter, setterを定義することも可能
var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value)
    }
  • lateinitキーワード
    • プロパティは必ず初期化する必要があるが、DI(Dependency Injection)を利用している場合などはインスタンス生成時に値を設定することができない
    • そういう場合にlateinitを使って初期化を遅らせることができる
    • lateinitはvarでしか利用できない→初期化後に変更される可能性がある
    • 初期化前にアクセスすると例外が発生するため注意
class MyClass {
    lateinit var foo: String
}