初めましてKotlin

はじめに

Kotlinの基本的な文法等を学びます。 取り扱っていないトピックも多々あるかと思いますが、最低限持っていたい知識をまとめました。 また、所々Javaとの比較も織り交ぜております。

Hello, World!

まずはこれを書かないことにはプログラミングは始まらないですよね。 Javaと比べるとだいぶスッキリした印象です。

fun main() {
    println("Hello, World!")
}

それでは以降、基本的な文法などを見ていきます。

変数

変数宣言には大きく分けて2つの方法があります。 valは、読み取り専用の変数を宣言し、varは書き換え可能な変数を定義できます。

val a: Int = 1
a = 10 //valで宣言しているため、コンパイルエラーとなります

var b: Int = 2
b = 3

関数

Kotlinでの関数定義方法を見ていきます。 Kotlinでは予約後funを使用することで関数を定義できます。以下は、足し算をする関数です。

fun sum(a: Int, b: Int): Int {
    return a + b
}

なお、上記の関数は以下のように書くこともできます。 この場合、返却される型は推論されます。

fun sum(a: Int, b: Int) = a + b

呼び出し方法はJavaなどの構文と同じです。

fun main() {
    println(sum(3, 5)) // 8
}

分岐と繰り返し

プログラムの基本である分岐と繰り返しです。 これさえ書ければ大半のロジックは組めます。(多分)

分岐

if

Kotlinでは、ifを式として扱います。 そのため、下記のような書き方が可能となります。

fun main() {
    val a = 10
    val b = 20
    //ifを式で扱えるため、ifが値を返す
    val max = if (a < b) b else a
    println(max)
}

//以下は、比較のため、ifを文として扱った場合です。
//Kotlinでもこのように書けますが、IDEによっては「もっとシンプルに書けるよ」と警告が出ます。
if (a < b) {
    max = b
} else {
    max = a
}

when

ifで分岐が多くなってしまう場合はwhenが利用できます。 場面に合わせて効果的な方を使いましょう。

when(age) {
    in 0..3 -> println("baby")
    in 4..12 -> println("child")
    in 13..19 -> println("youth")
    in 20.. 100 -> println("adult")
}

なお、whenは値を取らなくとも使用できます。 この場合、各式でtrueとなった行の右辺が実行されます。 途中でtrueとなった場合は以降の行は評価されないことに注意しましょう。 (Javaなどのswith文で、breakがあることをイメージすると良いかもしれません)

when {
    age in 0..3 -> println("baby")
    age in 4..12 -> println("child")
    age in 13..19 -> println("youth")
    age in 20.. 100 -> println("adult")
}

繰り返し

for

forは、いくつか書き方があります。

fun forExample(){
    //1,2,3,4,5,6,7,8,9,10
    for (i in 1..10) {
        println(i)
    }
    
    //stepを使用すると、指定した数値だけ飛ばしていきます。
    //1,3,5,7,9
    for (i in 1..10 step 2) {
        println(i)
    }
    
    //downToを使用すると数値を逆順で出せます。
    //10,8,6,4,2,0
    for (i in 10 downTo 0 step 2) {
        println(i)
    }

    //コレクションの全要素に対して処理をする場合です。
    //Javaの拡張For文に似ていますね。
    var arr: Array<String> = arrayOf("one", "two", "three")
    for (s in arr) {
        println(s)
    }
}

while

whiledo whileJavaとほとんど同じようです。

fun whileExample() {
    var x :Int = 10
    while(x > 0) {
        x--
        println(x)
    }

    do {
        x++
        println(x)
    } while(x < 10)
}

FizzBuzzを書いてみよう

分岐と繰り返しが書ければFizzBuzzが書ける! というわけで、前述までの知識でFizzBuzzを書いてみましょう。

fun fizzBuzz(count : Int) {
    for (i in 1..count) {
        when {
            i % 15 == 0 -> println("FizzBuzz")
            i % 5 == 0 -> println("Fizz")
            i % 3 == 0 -> println("Buzz")
            else -> println(i)
        }
    }
}

もっといい書き方があれば教えてください

オブジェクト指向

クラスやインターフェースなど、オブジェクト指向の基本的な記述方法です。

クラスの宣言

classキーワードを使用することでクラスを宣言することができます。 以下、コンストラクタと初期化ブロック(init)を持つPersonクラスです。

class Person(val firstName: String, val lastName: String) {
    init{
        println("hello, $firstName")
    }
}

Kotlinでは、コンストラクタはクラス名の直後に記載します。(val firstName: String, val lastName: String)←この部分です。 また、initを使用することで、オブジェクト生成時の挙動を定義することもできます。

オブジェクトの生成

Kotlinではオブジェクトの生成時にnew演算子を用いません。 前述したPersonクラスを用いたオブジェクト生成方法を下記に示します。

fun main() {
    val tarou: Person = Person("tarou", "tanaka")
    println(tarou.firstName)
}

結果

hello, tarou  //initブロック内の実行結果
tarou //main関数内で実行したprintlnの結果

継承

クラスの継承方法です。 注目して欲しいのはopenキーワードです。 Javaと異なり、Kotlinでは全てのクラスはデフォルトでfinalとなっています。 そのため、継承をさせたい場合は親クラスにopenキーワードを付与する必要があります。

open class Parent(x: Int) {

}

class Child(x: Int) : Parent(x) {
    
}

インターフェース

KotlinにおけるインターフェースもJavaと大きく違いはありません。予約語interfaceを使用します。 以下のように抽象メソッドを定義できますし、具象メソッドも定義できます。

//インターフェース
interface Calculator {
    fun calc(a : Int, b: Int): Int
}
//実装クラス
class Plus : Calculator {
    override fun calc(a: Int, b:Int):Int {
        return a + b
    }
}

コレクション

使用頻度が高いデータ型の代表格であるコレクションについて、詳しくみておこうと思います。 kotlinでは、コレクションを変更可能か、変更不能かで明確に分けていることに注意しましょう。

リスト

fun main() {
    //MutableList<E>は名前の通りミュータブル(変更可能)なリスト
    val numbers1: MutableList<Int> = mutableListOf(1,2,3,4,5)

    //List<E>型はイミュータブル(変更不可)なリスト
    val numbers2: List<Int> = listOf(1,2,3,4,5)

    //numbers1はミュータブルなリストのため、要素の追加と削除が可能
    numbers1.add(6)
    numbers1.removeAt(3)
    
    //numbers2はイミュータブルなリストのため、参照用のメンバしか呼び出せない
    numbers2.size
    val n = numbers2[0]
}

セットとマップ

セットとマップもリストと同様にイミュータブルとミュータブルで分かれています。 また、下記のコードにあるように、mapの生成はkey to valueの形式で、1 to "one"のように書けます。

fun main() {
    //setの生成
    val set1: MutableSet<Int> = mutableSetOf(1,2,3,4,5)
    val set2: Set<Int> = setOf(1,2,3,4,5)
    
    //mapの生成
    val map1: MutableMap<Int, String> = mutableMapOf(1 to "one", 2 to "two")
    val map2: Map<Int, String> = mapOf(1 to "one", 2 to "two")
    //mapの要素へアクセスする場合
    val value = map2[1] // one
}

Null安全

Kotlinでは、Javaと異なりNullを許容する変数と許容しない変数を明示的に分けて書くことが可能となっています。そのため、コンパイル時点でNullPointerExceptionの危険性に気づくことができます。 公式ドキュメントにもあるように、プログラマを悩ませてきたNullPointerException撲滅のため、しっかり理解して上手に扱えるようにしましょう。(自戒)

基本

まずは基本的な使い方から見ていきます。 以下のように、変数宣言時に型指定において、末尾に?を付けるとNullを参照できる変数となります。

var hoge: String = "hoge"
hoge = null //コンパイルエラー!!

//String?とすることでnullが設定可能となる    
var huga: String? = "huga"
huga = null

null参照の可能性がある場合、コンパイルエラーとなる

例えば、以下のようなコードを考えてみましょう。 変数hugaString?型のため、nullがありうる値となっています。 そのため、huga.lengthの呼び出しの際にNullPointerExceptionが発生する可能性があることに気づくと思います。 Kotlinではこのような場合に、実行時ではなくコンパイル時にNPEを抑止できるようになります。

var huga: String? = getHuga()
if (huga.length > 5) { //hoge.lengthでNPEの恐れがあることをコンパイラが気づいてくれる!
    println("hugehuga")
}

Nullableに対する処置

さて、コンパイルエラーになってしまうのではしょうがないので、コードを修正しなければいけません。 いくつか方法がありますので順に見ていきましょう。

nullチェックをする

原始的な(?)やり方ではありますが、これが一番ポピュラーな方法でしょう。 nullチェックを一度噛ませれば当然NPEが起こらないことが保証できるので、コンパイラも怒ったりしません。

if (huga != null) {
    if (huga.length > 5) {
        println("hugehuga")
    }
}

セーフコール

?.を使用することで、その変数がnullの場合はnullを、nullでない場合は呼び出したメソッドに応じた値を取れるようになります。 なお、以下の例の場合に返却される値の型はInt?になることに注意しましょう。

fun main() {
    var huga: String? = null
    var len1 = huga?.length //len1はnullになる

    var hoge: String? = "hoge"
    var len2 = hoge?.length //len2は4
}

エルヴィス演算子

?:を使用することもできます。 ?:を使用すると、左辺がnullの場合に右辺で指定した値を式の結果として返すようになります。 なお、右辺の式は左辺がnullである場合にのみ評価されます。

//変数hugaがnullでなければlengthの値を、hugaがnullならば−1を返す
var len = huga?.length ?: -1

!!演算子

3つ目の方法として、!!を使用できます。1 この演算子を使用すると、その変数がnullかどうかに関わらず、メソッドを呼び出すことが可能となります。 つまり、NullPointerExceptionが発生する恐れがあるということです。 本来NPEの発生抑止を目的としているのがNull安全の考え方だと思いますので、可能な限り使用は控えた方がいいかと思います。

var hoge: String? = null
var len = hoge!!.length //NullPointerExceptionが発生してしまう!

セーフキャスト

上記3つとは趣旨が若干異なりますが、Kotlinではセーフキャストという仕組みを利用することで、ClassCastExceptionを抑止することができます。 キャスト時に、想定した型にキャストできない場合、通常ClassCastExceptionがスローされますが、as?を使用すると例外が発生しなくなります。

var str: String? = getHoge() as? String?

例外

例外の補足などを学びます。

try-catch

try-catchは、Javaと大きく違いありません。 ただし、try-catchもKotlinでは式として使用することができます。 単純な例を示します。

fun main() {
    val result: Int = try {
        something()
    } catch (e: NullPointerException) {
        0
    }
}

fun something():Int {
    return 10
}

他いろいろ

その他、Kotlinの細かい部分についてもほんの少し触れておきます。

コメント

コメントの書き方もJavaと同じですね。

//1行コメント
    
/*
複数行はこのように書く
 */

セミコロンは不要

お気づきの方も多いと思いますが、Javaと異なりKotlinでは行末のセミコロンは不要です。

終わりに

公式ドキュメントや書籍から、Kotlinの基礎的な知識を記載しました。 適宜更新していこうと思います!


  1. なんと発音すれば良いのだろうか