基本的な演算

1. 演算子

Kotlinでは一般的な演算がサポートされています。
それぞれの演算子には対応する予め決められたメンバ関数かエクステンション関数があります。
operator修飾子をつけて、これらの関数をオーバーロードすることができます。

2. 単項演算子

他のプログラミング言語でも一般的に利用できる演算子がサポートされています。

対応する関数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a– a.dec()
++a a.inc()
–a a.dec()

3. 二項演算

3-1. 算術演算子

算術演算を行う二項演算子も、単項演算子と同じように一般的なものがサポートされています。

対応する関数
a + b a.plus(b)
a – b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b), a.mod(b)

3-2. レンジ演算子とイン演算子

Kotlinには、範囲を表すためのRange演算子と範囲内にあるかどうかを判定するIn演算子があります。
これらを利用することで、条件式などをスマートに記述することができます。

対応する関数
a..b a.rangeTo(b)
a in b b.contains(a)
a !in b !b.contains(a)
fun main(args: Array) {
    check(5)
}

fun check(x: Int) {
    if (x in 0..9) {
        println("a is in 0 to 9.")
    }
    else {
        println("a is not in 0 to 9.")
    }
}
a is in 0 to 9.

3-3. インデックスアクセス演算子

インデックスアクセス演算子はいわゆる[i]で配列やコレクションの要素にアクセスするための演算子です。
Kotlinでは要素の一部に対するアクセスのサポートされています。

対応する関数
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, … , i_n] a.get(i_1, … , i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, … , i_n] = b a.set(i_1, … , i_n, b)

3-4. 代入演算子

対応する関数
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign8b), a.modAssign(b)

3-5. 比較演算子

対応する関数
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null)
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

4. operator演算子

この章の冒頭でもご紹介したように、oprator演算子をつけて対応する関数を作成することで、
自作クラスでも演算子が使えるようになります.

fun main(args: Array) {
    val a = Point3D(10.0, 20.0, 30.0)
    val b = Point3D(1.0, 2.0, 3.0)

    println(a)
    println(-a)
    println(a - b)
    println("x: ${a[0]}, y: ${a[1]}, z: ${a[2]}")

    a[0] = 100.0
    a[1] = 200.0
    a[2] = 300.0
    println(a)
}

data class Point3D(var x: Double, var y: Double, var z: Double) {
    operator fun unaryMinus() = Point3D(-x, -y, -z)
    operator fun minus(b: Point3D) = Point3D(x - b.x, y - b.y, z - b.z)
    operator fun get(i: Int): Double? = when (i) {
        0 -> x
        1 -> y
        2 -> z
        else -> null
    }
    operator fun set(i: Int, value: Double) = when (i) {
        0 -> {
            x = value
        }
        1 -> {
            y = value
        }
        2 -> {
            z = value
        }
        else -> {
            print("out of range.")
        }
    }
}
Point3D(x=10.0, y=20.0, z=30.0)
Point3D(x=-10.0, y=-20.0, z=-30.0)
Point3D(x=9.0, y=18.0, z=27.0)
x: 10.0, y: 20.0, z: 30.0
Point3D(x=100.0, y=200.0, z=300.0)

5. ビット演算

ビット演算には+や/=のような特別な記号ははく、中置記法も利用できる関数としてサポートされています。

関数 内容
shl(bits) 符号付き左シフト(Javaでいう <<)
shr(bits) 符号付き右シフト(Javaでいう >>)
ushr(bits) 符号なし右シフト(Javaでいう >>>)
and(bits) ビット論理積
or(bits) ビット論理和
xor(bits) ビット排他的論理和
inv(bits) ビット反転
fun main(args: Array) {
    val a = 1
    println(a)

    // 通常の関数のように利用することも可能
    println(a.shl(1))
    println(a.shl(2))

    // 中置記法で記述も可能
    println(a shl 3)
    println(a shl 4)
}

データ型 (文字 / 文字列)

1. 文字型

Charが文字型です。C言語などと違い、直接的に数値として扱うことはできません。

文字リテラルはシングルクォートで囲います。
バックスラッシュを利用することで、エスケープシーケンスを利用することもできます。

\t 水平タブ
\b バックスペース
\n 改行
\r 復帰
\’ シングルクォーテーション
\” ダブルクォーテーション
\\ バックスラッシュ
\$ ダラーマーク
\u[unicode] Unicode
fun main(args: Array) {
    val c = 'a'

    // これはビルドエラー
    // if (c == 0)
    //     println("cは0です")
    // else
    //     println("cは0以外です")

    // 明示的に変換する必要がある
    if (c.toInt() == 0)
        println("cは0です")
    else
        println("cは0以外です")

    // エスケープシーケンスを利用する
    val cArray = arrayOf('\u3053', '\u3068', '\n', '\t', '\u308A', '\u3093')

    for(c in cArray) {
        print(c)
    }
}
cは0以外です
こと
    りん

2. 文字列

Stringが文字列型です。文字列型はイミュータブルで、各要素の文字にはstr[index]のようにアクセウできます。
また、forループで各要素をイテレーションすることもできます。

文字列リテラルには2種類あります。
1つはダブルクォートで囲うもので、Javaなどにもある一般的な文字列リテラルです。
もう1つは、トリプルクォート(“””some string”””)で囲うもので、
バックスラッシュによるエスケープなしに目で見たままの文字列を表現できます

fun main(args: Array) {
    val str = "Hello\n\tWorld!"
    println(str)

    println("-----------")

    for(c in str) {
        println(c)
    }

    println("-----------")

    // コード上の見た目そのままに入力されてしまうため、
    // コード上の字下げやインデントなどもそのまま表現されてしまう
    val str2 = """
        Hello
            World!
        """

    println(str2)

    println("-----------")

    // マージンプレフィックスをつけてtrimMarginをすることで
    // 簡単に見た目をきれいにできる
    val str3 = """
        |Hello
        |   World!
        """

    print(str3.trimMargin())
}
Hello
    World!
-----------
H
e
l
l
o



W
o
r
l
d
!
-----------

        Hello
            World!

-----------
Hello
    World!

4. 文字列テンプレート

ここまでも何度か登場してきましたが、
文字列の中に${式}とするとこで式を評価した結果を文字列にすることができます。

fun main(args: Array) {
    val str = "TestString"
    val num = 10

    println("${str}の長さは${str.length}です")
    println("${num}を2倍にした値は${num * 2}です")
}
TestStringの長さは10です
10を2倍にした値は20です

$を文字列テンプレートではなく、そのままの文字として扱いたい場合はエスケープシーケンスを使います。
fun main(args: Array) {
    val str = "TestString"

    println("\${str}の結果は${str}です")
}
${str}の結果はTestStringです

また、トリプルクォートでも同じように文字列テンプレートを使えますが、
エスケープシーケンスを使うことができないため、次のようにします。

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

    println("""${'$'}{str}の結果は${str}です""")
}
${str}の結果はTestStringです

データ型 (数値 / 真偽値 / 配列)

1. Kotlinのデータ型

Kotlinでは、基本的にすべてメンバ関数やプロパティといったものを備えるオブジェクトとして扱われます。
数値型やブール型、文字列型などは実行時にはプリミティブとして扱われますが、
プログラマーとしては通常のクラスと同じように使うことができます。

2. 数値型

数値型はJavaとほとんど同じですが違いもあります。
最大の違いは暗黙型変換がないことです。

fun main(args: Array) {
    val numInt: Int = 100

    // 以下の式はビルドエラーになる
    // val numLong: Long = numInt
}

数値型には次のようなものがあります。。

Type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

3. 数値リテラル

Kotlinでは8進数はサポートされていません。

10進数 123 (デフォルトはIntで、明示的にLongを指定したい場合は 123L とする)
16進数 0x3F
2進数 0b010111010
浮動小数 123.45 (デフォルトはDoubleで、明示的にFloatにしたい場合は 123.45f/123.45F とする)

また、数値の区切りとしてアンダースコアを利用することができます。

123_000_000
0b00001111_11110000_11000011

4. プリミティブ型と参照型

数値型て特に注意すべき点は、Java Platformの場合、
nullableでない数値型(例えばInt)は自動的にプリミティブ型として扱われますが、nullable(例えばInt?)は参照型になることです。

以下のサンプルコードを確認するとわかりやすいと想います。

fun main(args: Array) {
    val num1: Int = 12345
    val num2: Int = 12345

    println("プリミティブ型 値が同じか確認: ${num1 == num2}") // == は値の比較
    println("プリミティブ型 参照先が同じか確認: ${num1 === num2}") // === は参照の比較

    val numNullable1: Int? = num1
    val numNullable2: Int? = num1

    println("参照型 オブジェクト値が同じか確認: ${numNullable1 == numNullable2}") // == は値の比較
    println("参照型 参照先が同じか確認: ${numNullable1 === numNullable2}") // === は参照の比較
}
プリミティブ型 値が同じか確認: true
プリミティブ型 参照先が同じか確認: true
参照型 オブジェクト値が同じか確認: true
参照型 参照先が同じか確認: false

このように、参照型であるnullable(int?)は値が同じであっても、
インスタンスの実体が別にあるため === で比較するとfalseになります

5. 明示的型変換

Kotlinでは、演算の際にサイズの小さい型から大きい型へ暗黙的に型変換されません。
そのため、明示的に変換する必要があります。

fun main(args: Array) {
    val numInt: Int = 123
    val numLong: Long = 123

    // これは型が違うため、ビルドエラー
    // println("IntとLongの比較: ${numInt == numLong}")

    println("変換して値を比較: ${numInt.toLong() == numLong}")
}
変換して値を比較: true

型を変換するための関数は以下の通りです。

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

3. 真偽値

真偽値はBooleanです(Boolではありません)。
値はtrueとfalseです。
数値型と同じようにnullableの場合は参照型、そうでない場合はプリミティブ型になります。

|| 論理和(遅延評価)
&& 論理積(遅延評価)
! 否定

真偽値の演算である||と&&は遅延評価であるため、
それぞれのオペランドの評価は実際に評価が必要になるまで評価されません(必要になるまで評価を遅延させる)。
たとえば、&&では左辺の評価結果がfalseだった時点で右辺の評価をしなくても全体でfalseになることが確定します。
そうすると、右辺は評価する必要がなくなるため、実行されません。

fun main(args: Array) {
    val num1 = -5
    val num2 = 10

    // 左辺 isPositive(num1) を評価した結果がfalseだった時点で、
    // isPositive(num1) && isPositive(num2) は必ずfalseとなるため
    // 右辺 isPositive(num2) は評価(実行)されない
    if (isPositive(num1) && isPositive(num2)) {
        println("両方とも正です")
    } else {
        println("どちらかが正ではありません")
    }
}

fun isPositive(num: Int): Boolean {
    val ret = num > 0

    if (ret) {
        println("${num}は正です")
    } else {
        println("${num}は正ではありません")
    }

    return ret
}

-5は正ではありません
どちらかが正ではありません

5. 配列

配列はArrayクラスという形でサポートされています。
Arrayクラスはget(index), set(index)関数があります
(次項で説明しますが、これは[]オペレータでアクセスできるということです)。

Arrayクラスはコンストラクタがprivateであるため、直接生成することはできません。
Arrayクラスを生成するにはArrayOf()かArrayNullOf()を利用します。

Kotlinって何だろう?

1. Kotlinとは何か?

Kotlin(コトリン)はロシアのJetBrains社によって開発された
モダンなマルチプラットフォームアプリ向けの静的型付けなプログラミング言語です。

公式サイト

公式サイトのドキュメントなど

2. Kotlinで何ができるのか?

Webアプリではクライアント、サーバーの両方に対応し、モバイルではAndroidアプリを作成できます。
また様々なプラットフォーム向けのネイティブアプリの開発もできます。

JVM上で動作するアプリ

Java仮想マシン(Java Virtual Machien)上で動作するアプリケーションの作成を行うことができます。
Javaで作成されているレガシーなライブラリとも完全に相互運用できます。

Androidで動作するアプリ

Androidアプリを作成できます。
上記のように、すでにJavaで作成されている様々な有用なライブラリをすべて利用することができます。

Webブラウザで動作するアプリ

KotlinはJVMだけと思われている方もいるかもしれませんが、JavaScriptに変換しビルドすることができます。

仮想マシンを利用しないネイティブなアプリ

対応しているプラットフォームは以下のとおりです。

  • Windows(x86_64)
  • Linux(x86_64, arm32, MIPS, MIPS little endian)
  • MacOS(x86_64)
  • iOS(arm64)
  • Android(arm32, arm64)
  • WebAssembly(wasm32)

3. なぜKotlinを利用するのか?

Javaでいいじゃん。と思われる方も多いのではないでしょうか?
しかし、Javaに比べてKotlinはプログラミング言語としての設計がモダンなため、
Javaを利用するよりも開発の生産性をあげる事ができます。
Kotlinの主なメリットは下記のとおりです。

簡潔でわかりやすいコーディング

定形だけど冗長なコードを劇的に減らすことができます。
例えば、
getter/setter などを一行でコーディングできます。

型安全なコーディング

NullPointerExceptionが発生するかどうかは、従来、コーディング時にはわからず実行しないといけないものでしが。
しかし、Kotlinでは、Nullを許容するかどうかを型として指定できるため、コーディング時の絶対にNullにならないことをっ保証できます。

型のチェックをしてOKだった場合は、自動的に型変換される

JVM/Android/JavaScriptのライブラリとの100%の相互運用性

新しくKotlinを使い始めると、今までに開発したレガシーなライブラリや便利なフリーのライブラリが利用できないのではないかと心配になるかもしれません。
しかし、実はKotlinではまったく問題がありません。
KotlinはJVM/Android/JavaScriptのライブラリと100%の相互運用性があります。

開発環境が整っている

新しいプログラミング言語を利用したいと思ったときに、気になるのが満足できる開発環境があるかどうかです。
その点では、Kotlinはもともと開発環境メーカーであるJetBrainsが開発しているので、モダンな開発環境を利用できます。
JetBrainsの開発環境といえば「Python用のPyCharm」や「Android StudioのベースとなっているIntellij IDEA」などが有名ではないでしょうか?
Kotlinも無料のIntellij IDEA のCommunity Editionで開発が可能です。

4. Kotlinの情報は充実してる?

英語サイトなら充実してます。という回答になると想います。
ということで、このサイトでKotlinの入門的な情報を扱っていきたいと想います。

標準的なコーディングスタイル

1. Kotlinのコーディングスタイル

Kotlinにはオフィシャルなコーディングスタイルがあります。
基本的にはこのコーディングスタイルに従ってコーディングするのがいいでしょう。

標準コーディングスタイル(公式サイト)

2. ソースコード

まずは、ディレクトリやソースコードなどの構成のスタイルからです。

2-1. ディレクトリ構成

2-1. 複数の言語で混在するプロジェクトの場合

(Javaなどの他の言語と)ソースルートが同じにして、同じディレクトリ構造になるようにする

2-2. Kotlinだけのプロジェクトの場合

共通部分を除外したパッケージ構造と同じディレクトリ構造にする
(com.exampleがすべてのパッケージに共通する場合、com.exampleは除外したディレクトリ構造にする)
パッケージがcom.example.foo.barの場合、ソースコードは${source root}/foo/barに配置する

2-2. ソースファイル名

大文字から始まるキャメルケースで最後に.ktをつけます。
ソースファイルに一つのクラスが含まれる場合、そのクラスと同じ名前にします。
複数のクラスやトップレベルしかない場合は、内容を表す名前をつけます。

2-3. ソースファイル構成

同じソースファイルには、十分に意味的な関連のあるクラスやトップレベル要素を含めるようにします。
また、一つのソースファイルのサイズは数百行程度に収まるようにします。

クラスのエクステンションを定義スル場合、
そのエクステンションがそのクラスを利用するすべてのコードが必要とする場合にはもともとのクラスが記述されているソースファイルに、
そうでなくて特定のコードにのみ必要な場合は、利用するコードのあるソースファイルに記述します。
エクステンションように新しくソースファイルを作成することはしません。

2-4. クラスの構成

クラスを構成は以下のような順番で記述する

  • プロパティとイニシャライザ
  • セカンダリイニシャライ
  • メソッド(関連するものをまとめる。アルファペット順やヴィジビリティ順にしない)
  • コンパニオンオブジェクト?

2-5. インターフェース実装

インターフェースを実装する場合、インターフェースで記述されている通りの順番で各メンバーを実装していく

2-6. オーバーロード実装

オーバーロードは必ず、まとめて記述する

3. ネーミングルール

基本的にはJavaのネーミングルールを踏襲する。

3-1. パッケージ名

常に小文字でアンダースコアを利用しない

3-2. クラス・オブジェクト名

大文字から始まりキャメルケースで連結する

3-3. 関数・ローカル変数名

小文字から始まりキャメルケースで連結する
例外として、インスタンスを生成するファクトリ関数は、生成するインスタンスのクラスと同じ名前にする

3-4. テスト名

テスト名には以下の2つのタイプがあります。

  • バッククォートで囲んだ空白を含む文字列
  • アンダースコアを含む文字列

3-5. プロパティ名

  • 定数(constのついたプロパティ, valのトップレベルオブジェクト): すべて大文字でスネークケースで連結する
  • 通常のプロパティ: 小文字から始まるキャメルケースで連結
  • シングルトン: 大文字から始まるキャメルケースで連結
  • enum: すべて大文字でスネークケースで連結 or 大文字から初めてキャメルケースで連結

3-6. 名前の付け方

  • クラス: そのクラスを表す名詞か名詞句とする
  • メソッド: そのメソッドの動きを表す動詞か動詞句とする
  • 頭文字: 2文字の場合は大文字、それより多い場合は最初のみを大文字にする

4. フォーマット

基本的にはJavaのフォーマットを踏襲する。

4-1. インデント

インデントはスペース4文字分のスペースを使い、タブは使わない

4-2. 中括弧 {}

{ は改行せず、行末におく

4-3. スペース

  • レンジを除く2項演算子の前後にはスペースを入れる
  • 制御文(if, when, for, while)のあとにはスペースを入れる
  • ([ の後、])の前にはスペースを入れない
  • // のあとにはスペースを入れる
  • 水平方向への整列をしない。

4-4. コロン

型やスーパークラスを区切る場合にはコロンの前にスペースを入れる

忙しい人のためのコードスニペット

1. 基本的なコードスニペット

忙しい人や、Kotlinの雰囲気を知りたい人のためにKotlinの基本的な書き方のスニペットを集めました。

1-1. パッケージとインポート

package testpackage

fun testprint() {
    println("test print.")
} 
import testpackage.*

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

1-2. 関数

fun main(args: Array) {
    println("${add(10, 100)}")
    println("${sub(5, 20)}")
    printHello()
    printAdd(20, 30)
}

// fun 関数名(仮引数: 型): 返り値の型
fun add(a: Int, b: Int): Int {
    return a + b
}

// 関数が単独の式のみで構成される場合、{}が不要で、返り値は省略しても推定される
fun sub(a: Int, b: Int) = a - b

// 返り値なしは Unit
fun printHello(): Unit {
    println("Hello!")
}

// Unitは省略可能
fun printAdd(a: Int, b: Int) {
    println("$a + $b = ${a + b}")
}
110
-15
Hello!
20 + 30 = 50

1-3. 変数

val PI = 3.141592 // トップレベル定数
var topLevel = 100 // トップレベル変数

fun main(args: Array) {
    // 定数(val)
    val const1: Int = 100 // 定数
    val const2 = 123.45 // 宣言時に代入する場合、方は推定されるため省略可能
    val const3: String // 定数の宣言だけして、値の代入はあとですることも可能
    const3 = "constStr"

    println("const1 = $const1")
    println("const2 = $const2")
    println("const3 = $const3")

    // 変数(var)
    var num = 10 // 変数
    num += 10

    println("num = $num")

    // トップレベル
    println("PI = $PI")

    incrimentTopLevel()
    incrimentTopLevel()
    decrimentTopLevel()
    println("topLevel = $topLevel")
}

fun incrimentTopLevel() {
    topLevel++
}

fun decrimentTopLevel() {
    topLevel--
}
const1 = 100
const2 = 123.45
const3 = constStr
num = 20
PI = 3.141592
topLevel = 101

1-4. コメント

JavaやJavaScriptのコメントと変わりありませんが、ブロックコメントのネストが可能です

// 行末までコメント

/*
    ブロックコメント
    */

/*
    ブロックコメントは

    /*
        ネスト可能です
        */
    */

1-5. ストリングテンプレート

文字列の中に、任意の変数や式の値を埋め込む事ができます。書き方はLinuxっぽいですね

fun main(args: Array) {
    var realNum = 123.4
    // 単独の変数や定数の前に$をつける
    val realNumStr1 = "realNum = $realNum"

    // 任意の式を${}で囲う
    val realNumStr2 = "realNum * 2 = ${realNum * 2}"

    println(realNumStr1)
    println(realNumStr2)
}
realNum = 123.4
realNum * 2 = 246.8

1-6. 条件分岐(if)

いわゆるif文です。switchはKotlinにはありません。

fun main(args: Array) {
    val a = 123.4
    val b = 987.6

    println("max of (a, b) is ${max(a, b)}")
    println("min of (a, b) is ${min(a, b)}")

    rangeCheck(3, 2, 5)
}

fun max(a: Double, b:Double): Double {
    if (a > b) {
        return a
    } else {
        return b
    }
}

fun min(a: Double, b: Double): Double {
    // それぞれの式が一つだけの場合は次のように記述することできる
    return if (a < b)  a else b
}

fun rangeCheck(x: Int, min: Int, max: Int) {
    // レンジとif文の組み合わせて使う
    if (x in min..max) {
        println("$x is between $min to $max.")
    } else {
        println("$x is out of range.")
    }
}
max of (a, b) is 987.6
min of (a, b) is 123.4
3 is between 2 to 5.

1-7. 条件分岐(when)

fun main(args: Array) {
    parseToInt("Two")

    val obj1 = 15
    val obj2 = 123.45
    println("$obj1 is ${describe(15)}")
    println("$obj2 is ${describe(123.45)}")
}

fun parseToInt(str: String): Int? {
    // いわゆるswitchのような使い方ができます
    var num: Int? = null
    when (str) {
        "One" -> {
            num = 1
            println("This is $num.")
        }

        "Two" -> {
            num = 2
            println("This is $num.")
        }

        else -> {
            println("invalid String.")
        }
    }

    return num
}

fun describe(obj: Any): String =
    // このように柔軟な条件を追加することができ、
    // ifと同じように単独の式であれば値を返すことができます
    when (obj) {
        "some string" -> "some string"
        10 -> "Ten"
        is Double -> "Double"
        in 11..20 -> "between 11 to 20"
        1, 2 -> "One or Two"
        else -> "Unknown"
    }
This is 2.
15 is between 11 to 20
123.45 is Double

1-8. 繰り返し(for)

fun main(args: Array) {
    val studentList = listOf("鈴木太郎", "山田花子", "佐藤一郎")

    // コレクション
    for (student in studentList) {
        println(student)
    }

    println("-------------------")

    // レンジ
    for (x in 1..5) {
        println(x)
    }

    println("-------------------")

    for (x in 1..10 step 3) {
        println(x)
    }

    println("-------------------")

    for (x in 15 downTo 0 step 4) {
        println(x)
    }
}

1-9. コレクション

fun main(args: Array) {
    val studentList = listOf("鈴木太郎", "山田花子", "佐藤一郎")

    // コレクションを繰り返しで使う
    for (student in studentList) {
        println(student)
    }

    // コレクションを条件分岐で使う
    if ("鈴木太郎" in studentList) {
        println("鈴木太郎は生徒の一人です")
    }
}
鈴木太郎
山田花子
佐藤一郎
鈴木太郎は生徒の一人です

1-10. nullableとnull-safety

型の後ろに?をつけると、nullable(値がnullである可能性がある)となります。
逆に言えば、型の後ろに?が着いていないものは絶対にnullではありません。
nullableの場合、nullであると問題の発生するコードはビルド時にエラーになります。
(Javaの場合、nullチェックそしていないなどの問題があることが実行時にならないとわかりませんでした)
その為、null-safetyなプログラミングが可能です。

fun main(args: Array) {
    printAdd1("10", "20")
    printAdd2("100", "23")
    printAdd1("ten", "20")
}

fun parseToInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printAdd1(numStr1: String, numStr2: String) {
    val num1 = parseToInt(numStr1)
    val num2 = parseToInt(numStr2)

    // Int?であるnum1やnum2はnullの可能性があるため、
    // そのままnum1 * num2のように計算することはできない
    // println(num1 * num2) // これはビルドエラー

    // 次のようにnullチェックをすると、自動的にInt?からIntに変換される
    if (num1 != null && num2 != null) {
        println(num1 + num2)
    } else {
        println("num1 or num2 is null.")
    }
}

fun printAdd2(numStr1: String, numStr2: String) {
    val num1 = parseToInt(numStr1)
    var num2 = parseToInt(numStr2)

    // 次のように記述してもOKです
    if (num1 == null) {
        println("num1 is null.")
        return
    }

    if (num2 == null) {
        println("num2 is null.")
        return
    }

    println(num1 + num2)
}
30
123
num1 or num2 is null.

1-11. 型チェックとキャスト

fun main(args: Array) {
    println(parseToInt("100"))
    println(parseToInt(100))
    println(parseToInt("hundred"))
}

fun parseToInt(obj: Any): Double? {
    // is演算子で型のチェックをしてtrueだった場合、自動的にチェックした型にキャストされる
    if (obj is String) {
        // Stringにキャストされているので toDoubleOrNull を呼べる
        return obj.toDoubleOrNull()
    } else if (obj is Int) {
        // Intにキャストされているので toDouble を呼べる
        return obj.toDouble()
    } else {
        return null
    }
}
100.0
100.0
null
Scroll to top