例外

例外

Kotlinでは、例外クラスはすべて「Throwable」クラスを継承しています。
例外クラスは必ずメッセージとスタックトレースを持っています。
また、任意で例外がスローされた原因についての情報も持つことができます。
例外をスローするためには、「throw」式を利用します。
また、「try-catch(-finally)」でスローされた例外をキャッチします。

fun main(args: Array) {
    try {
        checkArgs(100)
    } catch (e: IllegalArgumentException) {
        println("catch block.")
        println(e.message)
        println(e.stackTrace)
    } finally {
        println("finally block.")
    }
}

fun checkArgs(num: Int) {
    if (num > 10) {
        throw IllegalArgumentException("num is too large.")
    }

    println("num is ${num}.")
} 
catch block.
num is too large.
[Ljava.lang.StackTraceElement;@49476842
finally block. 

catchブロックは何個あっても大丈夫です。
また、catchブロックやfinallyブロックは無くても構いませんが、catchブロックかfinallyブロックのいずれかは必要です。

また、tryは式ですので、if式やwhen式のように値を返します。

import kotlin.math.abs

fun main(args: Array) {
    val num1 = checkNum(-5)
    println("num1 is ${num1}")

    val num2 = checkNum(-12)
    println("num2 is ${num2}")
}

fun checkNum(num: Int) = try {
    if (num < -10 || 10 < num) {
        throw IllegalArgumentException("num is too large.")
    }

    abs(num)
} catch (e: IllegalArgumentException) {
    println(e.message)

    null
} 
num1 is 5
num is too large.
num2 is null 

チェック例外

結論から言うと、KotlinにはJavaのチェック例外に相当する機能はありません。
Kotlinの公式サイトでは、以下のJDKのインターフェースを例にして理由が挙げられています。

Appendable append(CharSequence csq) throws IOException; 

このインターフェースはStringBuilderのインターフェースですが、「append」には「throws IOException」というチェック例外が設定されています。
つまり、プログラマはStringBuilderや、
このインターフェースを継承している何らかのlogやconsoleクラスに文字列を追加するたびに「IOException」をキャッチしなければいけません。
そうすると、いたるところに次のようなコードが埋め込まれることになります。

try {
    log.append(message)
}
catch (IOException e) {
    // 絶対に例外は発生しない
} 

このような、例外をハンドリングしないキャッチブロックを作ることは、「Effective Java」などでも指摘されているように、お行儀がいいことではありません。
また、チェック例外については生産性の向上に寄与しないという批判的な意見も多いため、Kotlinでは採用していないようです。

ラムダ式 (その1)

高階関数

高階関数とは、ある関数を引数に持つような関数のことです。
関数を引数として渡すというのは、もう少し言うと関数の参照を渡すということです。
関数の参照は、関数名の前に「::」をつけることで取得できます。
例えば、次のサンプルのgetNonNullのような関数です。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    // 関数名の前に::をつけることで関数を参照を取得できる
    val subList = filter(fruits, ::isContainsP)

    println(subList)
}

fun filter(source: List, condition: (String) -> Boolean): List {
    val ret = mutableListOf()

    for (i in source) {
        if (condition(i)) {
            ret.add(i)
        }
    }

    return ret;
}

fun isContainsP(str: String): Boolean {
    // pを含むかどうかチェック。大文字小文字は無視する
    return str.contains('p', true)
} 
[apple, peach] 

ラムダ式

上記のサンプルの「isContainsP」ですが、たったこれだけの機能のためにわざわざ関数を作るのはめんどくさいです。
しかも、P以外のさまざまな条件も使いたいとなったときに、その条件の数だけ単純な関数を用意しようと思うと、もっとめんどくさいです。
そこで、単純な(式が1,2個程度の)関数を簡単便利に表現する機能があります。それがラムダ式です。
ラムダ式の基本的な書き方は以下の通りです。

{ 引数 -> 式 } 

引数の型は省略可能な場合もあります。
それでは、実際にサンプルを見てみましょう。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    // ラムダ式を利用すると簡単に記述できる
    val subList = filter(fruits, { str -> str.contains('p', true) })

    println(subList)
}

fun filter(source: List, condition: (String) -> Boolean): List {
    val ret = mutableListOf()

    for (i in source) {
        if (condition(i)) {
            ret.add(i)
        }
    }

    return ret;
} 
[apple, peach] 

it

高階関数の引数が1つのみの場合、その引数の宣言を(->を含めて)省略して「it」で表すことができます。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange", "peach")

    // 引数が一つのみの場合、省略してitで代用可能 
    val subList = filter(fruits, { it.contains('p', true) })

    println(subList)
}

fun filter(source: List, condition: (String) -> Boolean): List {
    val ret = mutableListOf()

    for (i in source) {
        if (condition(i)) {
            ret.add(i)
        }
    }

    return ret;
} 
[apple, peach] 

コレクション

Kotlinのコレクション

Kotlinでは、他の多くの言語と違い、コレクションがミュータブル(可変)であるかイミュータブル(不変)であるかを厳密に区別します。
イミュータブルなコレクションでは、sizeやgetなどの読み取り操作は可能ですが、要素の書き換えなどはできません。
また、実体がミュータブルでも、一部の場所では読取り操作しかさせたくない場合などは、
イミュータブルな変数として渡すことで読取り操作しかできないようにすることもできます。
jkk
コレクションにはList、Set、Mapがあり、それぞれにミュータブル版とイミュータブル版があります.

ミュータブル(型) イミュータブル(型) ミュータブル(生成) イミュータブル(生成)
List MutableList listOf() mutableListOf()
Set MutalbeList setOf() mutableSetOf()
Map MutalbeMap mapOf() mutableMapOf()
fun main(args: Array) {
    val fruits = mutableListOf("banana", "apple", "peach")
    val readOnlyViewOfFruits: List = fruits

    println(fruits)

    // fruitsはミュータブルなので要素の変更ができる
    fruits.add("melon")

    // Listは読取操作しかできないので、下記のコードはビルドエラーになる
    // readOnlyViewOfFruits.add("tomato")

    // readOnlyViewOfFruitsは読取り操作しかできないが、
    // 実体はfruitsなのでfruitsの変更が反映されている
    println(readOnlyViewOfFruits.size)

    val alphabets = setOf('a', 'b', 'c', 'd', 'c')
    println(alphabets.size)

    val productPrice = mapOf("Pen" to 200, "Note" to 300, "Coffee" to 100)
    println(productPrice["Coffee"])
} 
[banana, apple, peach]
4
4
100 

また、要素のクラスに継承関係があれば、次のような操作もできます。

import java.awt.Rectangle
import java.awt.Shape

fun main(args: Array) {
    val rects = listOf(Rectangle(1, 2, 3, 4), Rectangle(10, 20, 30, 40))
    val shapes: List = rects

    println(shapes)
} 
[java.awt.Rectangle[x=1,y=2,width=3,height=4], java.awt.Rectangle[x=10,y=20,width=30,height=40]] 

この例では、Shapeクラスを継承したRectangleクラスのListをShapeのクラスに代入しています。
代入した先のshapesの実体はrectsであるため、printlnするとrectangleの内容が出力されます。

型チェックとキャスト

is

ある変数の実体の型が何であるかを判定するためには「is」演算子を利用します。
is演算子でチェックされた変数は、自動的に判定された型にキャストされます。
これをスマートキャストといいます。

fun main(args: Array) {
    val str = "string"
    val obj: Any = str

    // objはAnyなので次の式はエラーになる
    // obj.length

    // is演算子で型をチェック
    if (obj is String) {
        // 明示的にキャストしなくても、objはStringに自動的にキャストされている
        println(obj.length)
    } else {
        println("obj is not String.")
    }

    // 否定も可能
    // !(obj is String)でもOK
    if (obj !is String) {
        println("obj is not String.")
    } else {
        // 明示的にキャストしなくても、objはStringに自動的にキャストされている
        println(obj.length)
    }

    // whenでも使える
    when (obj) {
        is Int -> println(obj * 2)
        is DoubleArray -> println(obj.average())
        is String -> println(obj.length)
    }
} 
6
6
6 

as

アンセーフキャスト

as演算子を利用すると、型キャストを行うことができます。
しかし、もしキャストに失敗した場合はClassCastExceptionという例外がスローされてしまいます。
例外は適切に処理されなければ不具合のもとになるため、Kotlinではアンセーフキャストと呼ばれています。

fun main(args: Array) {
    val obj: Any = "some string"

    // objはStringにキャストできる
    val str = obj as String
    println(str.length)

    val obj2: Any = 100

    // ClassCastExceptionが発生してしまう
    var str2 = obj2 as String
    println(str2.length)
} 
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at ******.main(*******.kt:11)
11 

セーフキャスト

一方で、「as?」演算子でnull安全の仕組みを利用して安全なキャストをすることができます.
こちらは、キャストしようとしてできなかった場合、nullが返ります。
つまり、そもそも適切に処理をしないとビルドエラーになり、実行することができません。
プログラマはコンパイラによって、きちんとnullチェックをすることが強制され、
うっかりによるチェックもれを防ぐことができるので「安全」だということです。

fun main(args: Array) {
    val obj: Any = "some string"

    // objはStringにキャストできる
    val str = obj as? String
    // strはString?であるため、nullチェックをしないと関数を呼べない
    // 下記のコードはビルドエラーになる
    // println(str.length)
    if (str != null) {
        println(str.length)
    } else {
        println("str is null")
    }

    val obj2: Any = 100

    // ClassCastExceptionが発生してしまう
    var str2 = obj2 as? String
    if (str2 != null) {
        println(str2.length)
    } else {
        println("str2 is null")
    }
} 
11
str2 is null 

制御のジャンプ

ジャンプとは

ジャンプとは、分岐や繰り返しなどの制御フローの途中に挿入することで、
関数やループを途中で抜けたり、あるいは特定の条件のときにループをスキップしたりすることができます。
Kotlinには制御をジャンプするために、次の式が用意されています。

説明
return デフォルトでは、returnを含む最も内側の関数、匿名関数から制御を返す
break そのbreakを含む最も内側のループを終了する
continue そのcontinueを含む最も内側のループに対して、そのループをスキップして次のループにすすめる
fun main(args: Array) {
    // ブレーク、コンティニューの条件をラムダ式で指定する
    val isBreak = { i: String -> i == "BreakStr" }
    val isContinue = { i: String -> i == "ContinueStr"}

    testLoop(null, isBreak, isContinue)
    testLoop(listOf("suzuki", "tanaka", "BreakStr", "yamada"), isBreak, isContinue)
    testLoop(listOf("suzuki", "tanaka", "ContinueStr", "yamada"), isBreak, isContinue)
}

fun testLoop(list: List?, isBreak: (String) -> Boolean, isContinue: (String) -> Boolean) {
    println("-------------------")
    if (list == null) {
        // listがnullの場合はこの関数を抜ける
        println("Return!")
        return
    }

    for (i in list) {
        if (isBreak(i)) {
            // ブレーク条件をみたす場合は、ループを終了する
            println("Break!")
            break
        }

        if (isContinue(i)) {
            // コンティニュー条件を満たす場合は、今回分の処理をスキップして、次の分の処理に進む
            println("Continue!")
            continue
        }

        println(i)
    }
} 
-------------------
Return!
-------------------
suzuki
tanaka
Break!
-------------------
suzuki
tanaka
Continue!
yamada 

ラベル付きbreak/continue

ループにラベルをつけることで、breakやcontimueで特定のラベルのループを抜けることができます。
ラベル@ラベル名で指定します。
これは、多重にネストしたループを一気に抜けたりする場合に便利です

fun main(args: Array) {
    val data = listOf(1, 2, 3,
                            4, 5, 6,
                            7, 0, 8,
                            9, 10, 11,
                            12, -1, 13,
                            14, 15, 16)

    labelLoop(data, 3, 6)
}

fun labelLoop(data: List, width: Int, height: Int) {
    if (data.size != width * height) return

    outerLoop@ for (y in 0 until height) {
        for (x in 0 until width) {
            val d = data[x + y * width]
            if (d < 0) break@outerLoop
            if (d == 0) {
                print("\n")
                continue@outerLoop
            }

            print("${d} ")
        }

        print("\n")
    }
} 
1 2 3 
4 5 6 
7 
9 10 11 
12 

ラベル付きreturn

Kotlinでは、関数はネスト可能です。
通常のreturnは、そのreturnを囲む一番内側の(匿名)関数をリターンします。
次の例を見てみましょう。

fun main(args: Array) {
    outerFunc()
}

fun outerFunc() {
    println("Called outerFunc")

    fun innerFunc(value: Int) {
        println("   Called innerFunc")

        if (value < 0) {
            println("   Return innerFunc")
            // このリターンはinnerFuncを終了する
            // outerFuncは終了しない
            return
        }

        println("   End of innerFunc")
    }

    innerFunc(10)
    innerFunc(-5)

    println("End of outerFunc")
} 

この、そのreturnを囲むというのが特に重要な意味を持つのは、
ラムダ式の中にreturnがある場合です。
次のサンプルの動作を確認してみましょう。

fun main(args: Array) {
    outerFunc(listOf("suzuki", "yamada", "ReturnStr", "tanaka"))
}

fun outerFunc(list: List) {
    println("Called outerFunc")

    val outerVal = "outerFuncで宣言された変数"
    list.also {
        println("ラムら式はクロージャなので、${outerVal}にアクセスできる。")
    }.forEach {
        // このreturnは、outerFuncを直接リターンする
        if (it == "ReturnStr") return
        println(it)
    }

    println("End of outerFunc")
} 
Called outerFunc
ラムら式はクロージャなので、outerFuncで宣言された変数にアクセスできる。
suzuki
yamada 

もし、ラムダ式の中のreturnでラムダ式をリターンしたい場合は、ラベルを付ける必要があります。

fun main(args: Array) {
    outerFunc(listOf("suzuki", "yamada", "ReturnStr", "tanaka"))
}

fun outerFunc(list: List) {
    println("Called outerFunc")

    val outerVal = "outerFuncで宣言された変数"
    list.also {
        println("ラムら式はクロージャなので、${outerVal}にアクセスできる。")
    }.forEach returnLamda@{
        // このreturnは、このラムダ式だけをリターンできる
        if (it == "ReturnStr") return@returnLamda
        println(it)
    }

    println("End of outerFunc")
} 
Called outerFunc
ラムら式はクロージャなので、outerFuncで宣言された変数にアクセスできる。
suzuki
yamada
tanaka
End of outerFunc 

上記の例のように、明示的にラベルを作成しなくても、暗黙的ラベルを利用するとより簡単にリターンすることもできます。

fun main(args: Array) {
    outerFunc(listOf("suzuki", "yamada", "ReturnStr", "tanaka"))
}

fun outerFunc(list: List) {
    println("Called outerFunc")

    val outerVal = "outerFuncで宣言された変数"
    list.also {
        println("ラムら式はクロージャなので、${outerVal}にアクセスできる。")
    }.forEach {
        // 暗黙的ラベルを利用する
        if (it == "ReturnStr") return@forEach
        println(it)
    }

    println("End of outerFunc")
} 
Called outerFunc
ラムら式はクロージャなので、outerFuncで宣言された変数にアクセスできる。
suzuki
yamada
tanaka
End of outerFunc 

レンジ

レンジの基本的な使い方

レンジは数値の範囲を表すために利用する式です。
「..」演算子や「..」演算子に対応する関数である「rangeTo()」関数を「in」キーワードと組み合わせて利用します。
「a..b」で「aからbの間」という意味になります。
比較で利用する場合は、「a <= x && x <= b」で、繰り返しで利用する場合は「a, a + 1, ... , b - 1, b」となります。
レンジは標準ではInt型ですが、Doubleの値とも比較できます。

fun main(args: Array) {
    // .. 演算子でレンジを利用する
    for (i in 1..5) {
        println(i)
    }

    // rangeTo 関数でレンジを利用する
    for (i in 6.rangeTo(10)) {
        println(i)
    }

    // Doubleとも比較できる
    if (10.0 in 0..100) {
        println("in 0 to 100")
    }

    // 否定も可能です
    if (-123 !in 0..100) {
        println("not in 0 to 100")
    }
} 
1
2
3
4
5
6
7
8
9
10
in 0 to 100
not in 0 to 100 

レンジの応用的な使い方

downTo

繰り返しなどで数値を大きい方から小さい方にイテレーションしたい場合は「downTo」を使うことができます。

fun main(args: Array) {
    val start = 9
    val end = 0

    // 通常のレンジ演算子は大きい方から小さい方には使えない
    println("start..end")
    for (i in start..end) {
        print(i)
    }

    // downToを使うと大きい方から小さい方にイテレーションできる
    println("start downTo end")
    for (i in start downTo end) {
        print(i)
    }
} 
start..end
start downTo end
9876543210 

step

レンジで繰り返しをする場合に、数値を1ずつインクリメントするのではなく、任意の間隔で利用するには「step」を使います。

fun main(args: Array) {
    val num = 3

    // step で任意の間隔を指定する
    println("0..20 step num")
    for (i in 0..20 step num) {
       print("${i} ")
    }

    // downToにも利用できます
    println("\n20 downTo 0 step num")
    for (i in 20 downTo 0 step num) {
       print("${i} ")
    }
} 
0..20 step num
0 3 6 9 12 15 18 
20 downTo 0 step num
20 17 14 11 8 5 2 

until

JavaやC++では、「for (int i = 0; i < count; i++)」のようなループをよく使うと思います。 この時、繰り返し範囲は「0 <= i < count」のように終わりが開区間になっています。
一方、Kotlinのレンジは「0..count」とすると「0 <= i <= count」というようにcountも含んだ閉区間になります。
Kotlinで「0 <= i < count」のようにcountを含まない区間を取得するにはuntilを使います。

fun main(args: Array) {
    // until を使うと最後を含まない繰り返しができる
    println("0 until 10")
    for (i in 0 until 10) {
       print("${i} ")
    }
} 
0 until 10
0 1 2 3 4 5 6 7 8 9 

繰り返し

for

kotlinでのforはC#のforeachのようにコレクションの要素に順番にアクセスできます。
もちろん、C言語などでよくあるN回繰り返すというような処理も可能です。
そのような場合にはレンジを利用します。

fun main(args: Array) {
    val fruits = listOf("apple", "banana", "orange")

    // コレクションの要素を順番に取り出す
    for (item in fruits) {
        println(item)
    }

    // インデックスを取得して要素にアクセスすることもできる
    for (i in fruits.indices) {
        println(fruits[i])
    }

    // 要素とインデックスを同時に取り出すこともできます
    for ((index, item) in fruits.withIndex()) {
        println("${index}: item")
    }

    // レンジをりようし利用して指定回数繰り返す
    val num = 10
    var sum = 0
    for (i in 0 until num) {
        sum += i
        println("sum = ${sum}")
    }
} 
apple
banana
orange
apple
banana
orange
0: item
1: item
2: item
sum = 0
sum = 1
sum = 3
sum = 6
sum = 10
sum = 15
sum = 21
sum = 28
sum = 36
sum = 45 

while

while、do…whileループはJavaなどと同様に使うことができます。
do…whileでは、doブロック内で宣言した変数は、
whileの条件判定部のスコープになります。

val data = mutableListOf(1, 2, 3, 4)

fun main(args: Array) {
    var num = 1

    while (num < 100) {
        num += num;
        println(num)
    }

    do {
        val num = fetchData()
        println(num)
    } while (num != null) // ここでもnumを参照することができます
}

fun fetchData(): Int? =
    if (data.isEmpty()) {
        null
    } else {
        val ret = data[0]
        data.removeAt(0)

        ret
    } 
2
4
8
16
32
64
128
1
2
3
4
null 

条件分岐

if

基本的な使い方

Kotlinでは、ifは式として機能しています。
つまり、ifが値を返して、その値をそのまま変数に代入したりできるということです。
もちろん、通常の条件分岐としても利用できます。

fun main(args: Array) {
    // 通常の使い方
    val num = 10

    if ((num % 2) == 0) {
        println("偶数です")
    } else {
        println("奇数です")
    }

    // 式としての使い方
    val num2 = 5
    val str = if ((num2 % 2) == 0) "偶数です" else "奇数です"

    println(str)
} 
偶数です
奇数です 

リターンされる値

ifでリターンされる値は、実行されたブロックの一番最後の式の値になります。

fun main(args: Array) {
    testIf(-100)
    testIf(50)
    testIf(1)
}

fun testIf(num: Int) {
    val ifReturn = if (num < 0) {
        println("Run (num < 0) block.")

        num * 2
    } else if ((num % 2) == 0) {
        println("Run ((num % 2) == 0) block.")

        "偶数です"
    } else {
        println("Run else block.")

        1234.5678
    }

    println("if expression return is ${ifReturn}.")
} 
Run (num < 0) block.
if expression return is -200.
Run ((num % 2) == 0) block.
if expression return is 偶数です.
Run else block.
if expression return is 1234.5678. 

when

基本的な使い方

whenはJavaやC言語でいうとswitchのようなものです。
ifと同じように式として機能し、値を返すことができます。

switchのdefaultに相当するものが、whenにもelseという形であります。

fun main(args: Array) {
    val num = 4

    // 通常の条件分岐として利用
    when (num) {
        1 -> println("num is 1.")
        2 -> println("num is 2")
        3 -> println("num is 3.")
        4 -> {
            // 処理をブロックで記述することも、もちろん可能です。
            println("num is 4.")
        }
        else -> {
            println("num is something else.")
        }
    }

    // 値を返す式として利用
    val ret = when (num) {
        1 -> {
            println("num is 1.")

            123
        }
        2 -> {
            println("num is 2.")

            "456"
        }
        3 -> {
            println("num is  3.")

            123.456
        }
        else -> {
            println("num is something else")

            null
        }
    }

    println("ret is ${ret}.")
} 
num is 4.
num is something else
ret is null. 

さまざまな条件設定

whenでは、かなり柔軟な分岐の条件設定ができます。
例えば、カンマで区切って複数の条件を並べたり、定数ではなく式を条件にすることもできます。

fun main(args: Array) {
    val str = "100"
    val num = 100.0
    str.isEmpty()

    when (str) {
        "cat", "dog", "bird" -> println("animal")
        num.toString() -> println("100.0")
        is String -> println("String")
        else -> println("else.")
    }
} 
String 

Null安全

null安全について

JavaやC++、Objective-Cなどの言語では、プログラムを実行するまでオブジェクトがnullであるかどうかがわからない場合があります。
そのため、プログラマーは手動でオブジェクトがnullでないことをチェックするコードを作成して対策する必要があるのですが、
実際にはその対策が不十分であったことで、数多くの致命的な不具合や脆弱性の原因になっています。

この問題は、ビリオンダラーミステイクと言われたこともあります。
(以下に、クイックソートの発明などでも知られる計算機科学者であるアンソニー・ホーアという人の言葉を引用したいと思います)

それは10億ドルにも相当する私の誤りだ。
null参照を発明したのは1965年のことだった。
当時、私はオブジェクト指向言語 (ALGOL W) における参照のための包括的型システムを設計していた。
目標は、コンパイラでの自動チェックで全ての参照が完全に安全であることを保証することだった。
しかし、私は単にそれが容易だというだけで、無効な参照を含める誘惑に抵抗できなかった。
これは、後に数え切れない過ち、脆弱性、システムクラッシュを引き起こし、過去40年間で10億ドル相当の苦痛と損害を引き起こしたとみられる。


Wikipedia

プログラム実行時に誤ってNullを参照することで発生する様々な不具合を回避するため、
最近のプログラミング言語では(プログラムを実行しなくても)ビルド時にnullでないことを保証する「null安全」という仕組みが取り入れられています。
つまり、nullになる可能性になるオブジェクトを(もしオブジェクトがNullだったらまずいような)操作しようとするとビルドエラーが発生するようになっているといういことです。

これが、「null安全」と呼ばれる仕組みです。

nullableとnon-null

nullableな変数の宣言

null安全なプログラミング言語であるKotlinにおいて、
nullable(nullを代入できる)な型とnon-null(nullを代入できない)な型はまったく異なるものです。
「?」が付いている型がnullableになります。

fun main(args: Array) {
    var nullableString: String?

    // nullableな型の変数には、nullを代入できる
    nullableString = null
    println("nullableString=${nullableString}")

    // もちろん、nullでない値を代入することもできる
    nullableString = "nullableです"
    println("nullableString=${nullableString}")

    var nonnullString: String

    // nonnullな方には、nullを代入できない。ビルドエラーになる
    // nonnullString = null

    // nullでない値しか代入できない
    nonnullString = "nonnullです"
    println("nonnullString=${nonnullString}")
} 
nullableString=null
nullableString=nullableです
nonnullString=nonnullです 

nullableな変数は、その関数を呼び出したりすることができません。
これは、もしその変数がnullだった場合、NGだがらです。

fun main(args: Array) {
    val nullableString: String? = "nullableです"

    // nullableな変数からは、関数を呼び出すことはできません
    // ビルドエラーになります。
    // println(nullableString.length)
} 

明示的にnullチェックする

nullableな変数を利用するためのもっとも基本的な方法は、ifを利用してnullチェックをすることです。
コンパイラが自動的にnullチェックされているかどうかを判別してくれます。
nullチェックしていれば関数を呼んだりすることができます。

fun main(args: Array) {
    val nullableString: String? = "nullableです"

    // ifで明示的にnullチェックする
    if (nullableString != null) {
        // コンパイラがnullチェック済みかどうかを判定して
        // nullチェック済みならメンバーを利用できる
        println(nullableString.length)
    } else {
        println("nullableString is null.")
    }
} 
10 

セーフコール

nullableを利用するための2つ目の方法は、セールコールを利用することです。
セーフコールはnullableな変数の後ろに?をつけてメンバを呼ぶことで、
もしその変数がnullでなければ通常の結果が、そうではなくnullならnullが返ってきます。

fun main(args: Array) {
    val nullableString: String? = "nullableです"
    println("nullableString.length = ${nullableString?.length}")

    // nullableなリストの要素にアクセスする
    val fruits = listOf("apple", null, "orange")
    for (fruit in fruits) {
        println(fruit?.length)
    }
} 
nullableString.length = 10
5
null
6 

エルビス演算子

エルビス演算子はifを利用したnullチェックの表現をシンプルにしたものです。
C++なでにある三項演算子?をnull専用にしたようなものをイメージすればいいと思います。
エルビス演算子は「?:」で表現されます。
単純なセーフコールとの違いは、実際にnullだった場合にどんな値を返すかをプログラマが指定できることです。

fun main(args: Array) {
    val nullableString: String? = "nullableです"

    // もしnullableがnon-nullだった場合はnullableString.lengthが,
    // そうでなくてnullだった場合は-1が返る
    val length = nullableString?.length ?: -1
    println("nullableString.length = ${length}")

    // nullableなリストの要素にアクセスする
    val fruits = listOf("apple", null, "orange")
    for (fruit in fruits) {
        println(fruit?.length ?: -1)
    }
}
nullableString.length = 10
5
-1
6 

!!演算子

「!!」(非nullアサーション演算子)はnullableな変数の後ろにつけることで、
もし本当に変数がnullだった場合には、自動的に「null pointer exception」をスローします

fun main(args: Array) {
    val nullableString: String? = "nullableです"

    // もしnullableがnon-nullだった場合はnullableString.lengthが,
    // そうでなくてnullだった場合は-1が返る
    val length = nullableString!!.length
    println("nullableString.length = ${length}")

    // nullableなリストの要素にアクセスする
    val fruits = listOf("apple", null, "orange")
    for (fruit in fruits) {
        println(fruit!!.length)
    }
} 
nullableString.length = 10
Exception in thread "main" kotlin.KotlinNullPointerException
	at ***********.main(***********.kt:13)
5 

セーフキャスト

「as」演算子を利用してキャストしようとして失敗した場合、
通常はClassCastExceptionがスローされます。
セーフキャストを利用することで、例外をスローせずに安全にキャストすることができます。
セーフキャストは「as?」とすることで利用でき、もしキャストできない場合は、例外をスローする代わりにnullを返します。

fun main(args: Array) {
    val str = "string"

    // キャストに失敗するとnullが返却される
    val num: Int? = str as? Int
    println("num = ${num}")

    // キャストに失敗するとClassCastExceptionがスローされる
    val num2 = str as Int
    println("num2 = ${num2}")
} 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at *********.main(HelloWorld.kt:7)
num = null 

関数

関数の基本的な使い方

Kotlinでは、「fun」キーワードで関数を宣言します。

fun 関数名(引数1: 引数1の型, 引数2: 引数2の型): 戻り値の型 {
    // 処理
    return 戻り値
} 

引数や戻り値は、それらがなければ省略することができます。

fun 関数名() {
    // 処理
} 

また、戻り値がないことは「Unit」を使って明示することもできます。

fun 関数名(): Unit {
    // 処理
} 

それでは、サンプルプログラムを見てみましょう。

fun main(args: Array) {
    println("${plus(10, 20)}")
    printDouble(100)
    printHalf(23)
}

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

fun printDouble(a: Int) {
    println("Double: ${a * 2}")
}

fun printHalf(a: Int): Unit {
    println("Half: ${a.toDouble() / 2.0}")
} 
30
Double: 200
Half: 11.5 

デフォルト引数

関数の引数にはデフォルトの値を設定できます。
デフォルト引数は、関数を呼び出すときに別の値を渡す必要がなければ省略できます。

fun main(args: Array) {
    println("${plus(5)}")
}
    
fun plus(a: Int, b: Int = 10): Int {
    return a + b
} 
15 

デフォルト引数は、デフォルトの値を持たない引数の前にあっても問題ありません。
ただし、関数を呼び出すときに、引数名をつけて値を渡す必要があります。

fun main(args: Array) {
    println("${plus(b = 30)}")
}

fun plus(a: Int = 5, b: Int): Int {
    return a + b
} 
35 

名前付き引数

上記のデフォルト引数のところでも紹介しましたが、関数を呼び出すときに、
引数名を指定して値を渡すことで、特定の引数だけに値を渡すことができます。
たくさんあるデフォルト引数のうちの一部だけ別の値を指定したい時などに便利です。

fun main(args: Array) {
    plus(isPrint = true)
}

fun plus(a: Double = 1.0, b: Double = 1.0, isPrint: Boolean = false): Double {
    val result = a + b

    if (isPrint) {
        println("a = ${a}, b = ${b}, a * b = ${result}")
    }

    return result
} 
a = 1.0, b = 1.0, a * b = 2.0 

式が一つだけの関数

関数にしたい式が一つだけの場合、{}やreturn記述を省略する子ができます。

fun main(args: Array) {
    println("${plus(10.0, 5.0)}")
}

fun plus(a: Double, b: Double): Double = a + b 
15.0 

可変長引数

vararg修飾子を引数につけることで、可変個の引数を関数に渡すことができます。
可変長引数は、関数の中では配列になります。

fun main(args: Array) {
    println("${plus(10.1, 20.2, 30.3, 40.4)}")
}

fun plus(vararg args: Double): Double {
    var sum = 0.0

    for (elem in args) {
        sum += elem
    }

    return sum
} 
101.0 

Infix

Infixと呼ばれる表記を関数にすることで、関数を2項演算子のように使うことができます。
引数を一つ持つことと、クラスのメンバ関数かエクステンションであることが条件です。

fun main(args: Array) {
    val result = 12 append 345

    println(result)

    // 普通に関数として呼び出すこともできます
    val result2 = 12.append(345)

    println(result2)
}

// Intに拡張関数を作る
infix fun Int.append(x: Int): Int {
    val origin = toString()
    val arg = x.toString()

    val appended = origin + arg

    return appended.toInt()
} 
12345
12345 
Scroll to top