メインコンテンツまでスキップ

Null safety

Null safetyは、The Billion-Dollar Mistakeとしても知られる、null参照のリスクを大幅に軽減するために設計されたKotlinの機能です。

Javaを含む多くのプログラミング言語における最も一般的な落とし穴の1つは、null参照のメンバーにアクセスすると、null参照例外が発生することです。Javaでは、これはNullPointerException、略して_NPE_に相当します。

Kotlinは、型システムの一部としてnull許容性を明示的にサポートしています。つまり、どの変数またはプロパティがnullを許可されるかを明示的に宣言できます。また、null非許容変数を宣言すると、コンパイラーはこれらの変数がnull値を保持できないように強制し、NPEを防止します。

Kotlinのnull safetyは、潜在的なnull関連の問題をランタイムではなくコンパイル時に検出することにより、より安全なコードを保証します。この機能は、null値を明示的に表現することで、コードの堅牢性、可読性、および保守性を向上させ、コードを理解および管理しやすくします。

KotlinでNPEが発生する可能性のある原因は次のとおりです。

ヒント

NPEに加えて、null safetyに関連するもう1つの例外はUninitializedPropertyAccessExceptionです。Kotlinは、初期化されていないプロパティにアクセスしようとすると、この例外をスローし、null非許容プロパティが準備できるまで使用されないようにします。これは通常、lateinit propertiesで発生します。

Nullable types and non-nullable types

Kotlinでは、型システムはnullを保持できる型(nullable types)と、そうでない型(non-nullable types)を区別します。たとえば、String型の通常の変数はnullを保持できません。

fun main() {

// Assigns a non-null string to a variable
var a: String = "abc"
// Attempts to re-assign null to the non-nullable variable
a = null
print(a)
// Null can not be a value of a non-null type String

}

aでメソッドを安全に呼び出したり、プロパティにアクセスしたりできます。aはnull非許容変数であるため、NPEが発生することはありません。コンパイラーは、aが常に有効なString値を保持することを保証するため、nullの場合にそのプロパティまたはメソッドにアクセスするリスクはありません。

fun main() {

// Assigns a non-null string to a variable
val a: String = "abc"
// Returns the length of a non-nullable variable
val l = a.length
print(l)
// 3

}

null値を許可するには、変数型の直後に?記号を付けて変数を宣言します。たとえば、String?と記述してnullable stringを宣言できます。この式により、Stringnullを受け入れることができる型になります。

fun main() {

// Assigns a nullable string to a variable
var b: String? = "abc"
// Successfully re-assigns null to the nullable variable
b = null
print(b)
// null

}

blengthに直接アクセスしようとすると、コンパイラーはエラーを報告します。これは、bがnullable変数として宣言されており、null値を保持できるためです。nullablesでプロパティに直接アクセスしようとすると、NPEが発生します。

fun main() {

// Assigns a nullable string to a variable
var b: String? = "abc"
// Re-assigns null to the nullable variable
b = null
// Tries to directly return the length of a nullable variable
val l = b.length
print(l)
// Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

}

上記の例では、コンパイラーは、プロパティにアクセスしたり操作を実行したりする前に、safe callsを使用してnullabilityを確認する必要があります。nullablesを処理する方法はいくつかあります。

null handlingツールとテクニックの詳細と例については、次のセクションをお読みください。

Check for null with the if conditional

nullable typesを使用する場合、NPEを回避するためにnullabilityを安全に処理する必要があります。これを処理する1つの方法は、if conditional式でnullabilityを明示的に確認することです。

たとえば、bnullかどうかを確認し、b.lengthにアクセスします。

fun main() {

// Assigns null to a nullable variable
val b: String? = null
// Checks for nullability first and then accesses length
val l = if (b != null) b.length else -1
print(l)
// -1

}

上記の例では、コンパイラーはsmart castを実行して、型をnullable String?からnon-nullable Stringに変更します。また、実行したチェックに関する情報を追跡し、if conditional内でlengthの呼び出しを許可します。

より複雑な条件もサポートされています。

fun main() {

// Assigns a nullable string to a variable
val b: String? = "Kotlin"

// Checks for nullability first and then accesses length
if (b != null && b.length > 0) {
print("String of length ${b.length}")
// Provides alternative if the condition is not met
} else {
print("Empty string")
// String of length 6
}

}

上記の例は、コンパイラーがチェックとその使用の間でbが変更されないことを保証できる場合にのみ機能します。smart cast prerequisitesと同じです。

Safe call operator

safe call operator ?.を使用すると、nullabilityをより短い形式で安全に処理できます。オブジェクトがnullの場合、NPEをスローする代わりに、?. operatorは単にnullを返します。

fun main() {

// Assigns a nullable string to a variable
val a: String? = "Kotlin"
// Assigns null to a nullable variable
val b: String? = null

// Checks for nullability and returns length or null
println(a?.length)
// 6
println(b?.length)
// null

}

b?.length式はnullabilityを確認し、bがnull非許容の場合はb.lengthを返し、それ以外の場合はnullを返します。この式の型はInt?です。

Kotlinではvar and val variablesの両方で?. operatorを使用できます。

  • nullable varは、null(たとえば、var nullableValue: String? = null)またはnull非許容値(たとえば、var nullableValue: String? = "Kotlin")を保持できます。null非許容値の場合、いつでもnullに変更できます。
  • nullable valは、null(たとえば、val nullableValue: String? = null)またはnull非許容値(たとえば、val nullableValue: String? = "Kotlin")を保持できます。null非許容値の場合、後でnullに変更することはできません。

safe callsはチェーンで役立ちます。たとえば、Bobは部署に割り当てられている(または割り当てられていない)従業員です。その部署には、別の従業員が部門長として配置されている場合があります。Bobの部門長の名前(存在する場合)を取得するには、次のように記述します。

bob?.department?.head?.name

このチェーンは、そのプロパティのいずれかがnullの場合にnullを返します。次に、同じsafe callと同等のif conditionalを示します。

if (person != null && person.department != null) {
person.department.head = managersPool.getManager()
}

割り当ての左側にsafe callを配置することもできます。

person?.department?.head = managersPool.getManager()

上記の例では、safe callチェーンのいずれかのレシーバーがnullの場合、割り当てはスキップされ、右側の式はまったく評価されません。たとえば、personまたはperson.departmentのいずれかがnullの場合、関数は呼び出されません。

Elvis operator

nullable typesを使用する場合、nullを確認して代替値を提供できます。たとえば、bnullでない場合は、b.lengthにアクセスします。それ以外の場合は、代替値を返します。

fun main() {

// Assigns null to a nullable variable
val b: String? = null
// Checks for nullability. If not null, returns length. If null, returns 0
val l: Int = if (b != null) b.length else 0
println(l)
// 0

}

完全なif式を記述する代わりに、Elvis operator ?:を使用して、より簡潔な方法でこれを処理できます。

fun main() {

// Assigns null to a nullable variable
val b: String? = null
// Checks for nullability. If not null, returns length. If null, returns a non-null value
val l = b?.length ?: 0
println(l)
// 0

}

?:の左側の式がnullでない場合、Elvis operatorはそれを返します。それ以外の場合、Elvis operatorは右側の式を返します。右側の式は、左側がnullの場合にのみ評価されます。

throwreturnはKotlinの式であるため、Elvis operatorの右側で使用することもできます。これは、たとえば、関数引数を確認する場合に役立ちます。

fun foo(node: Node): String? {
// Checks for getParent(). If not null, it's assigned to parent. If null, returns null
val parent = node.getParent() ?: return null
// Checks for getName(). If not null, it's assigned to name. If null, throws exception
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ...
}

Not-null assertion operator

not-null assertion operator !!は、任意の値をnull非許容型に変換します。

値がnullでない変数に!! operatorを適用すると、null非許容型として安全に処理され、コードは正常に実行されます。ただし、値がnullの場合、!! operatorはnull非許容として扱うことを強制し、NPEが発生します。

bnullでなく、!! operatorがそのnull非許容値(この例ではString)を返すようにすると、lengthに正しくアクセスします。

fun main() {

// Assigns a nullable string to a variable
val b: String? = "Kotlin"
// Treats b as non-null and accesses its length
val l = b!!.length
println(l)
// 6

}

bnullで、!! operatorがそのnull非許容値を返すようにすると、NPEが発生します。

fun main() {

// Assigns null to a nullable variable
val b: String? = null
// Treats b as non-null and tries to access its length
val l = b!!.length
println(l)
// Exception in thread "main" java.lang.NullPointerException

}

!! operatorは、値がnullではなく、NPEが発生する可能性がないと確信している場合に特に役立ちますが、コンパイラーは特定のルールによりこれを保証できません。このような場合、!! operatorを使用して、値がnullではないことをコンパイラーに明示的に指示できます。

Nullable receiver

nullable receiver typeで拡張関数を使用できます。これにより、これらの関数はnullである可能性のある変数で呼び出すことができます。

nullable receiver typeで拡張関数を定義することにより、関数を呼び出すすべての場所でnullを確認する代わりに、関数自体内でnull値を処理できます。

たとえば、.toString()拡張関数は、nullable receiverで呼び出すことができます。null値で呼び出されると、例外をスローせずに、文字列"null"を安全に返します。


fun main() {
// Assigns null to a nullable Person object stored in the person variable
val person: Person? = null

// Applies .toString to the nullable person variable and prints a string
println(person.toString())
// null
}

// Defines a simple Person class
data class Person(val name: String)

上記の例では、personnullであっても、.toString()関数は文字列"null"を安全に返します。これは、デバッグとロギングに役立ちます。

.toString()関数がnullable string(文字列の表現またはnull)を返すことを期待する場合は、safe-call operator ?.を使用します。?. operatorは、オブジェクトがnullでない場合にのみ.toString()を呼び出し、それ以外の場合はnullを返します。


fun main() {
// Assigns a nullable Person object to a variable
val person1: Person? = null
val person2: Person? = Person("Alice")

// Prints "null" if person is null; otherwise prints the result of person.toString()
println(person1?.toString())
// null
println(person2?.toString())
// Person(name=Alice)
}

// Defines a Person class
data class Person(val name: String)

?. operatorを使用すると、潜在的なnull値を安全に処理しながら、nullである可能性のあるオブジェクトのプロパティまたは関数にアクセスできます。

Let function

null値を処理し、null非許容型でのみ操作を実行するには、safe call operator ?.let functionとともに使用できます。

この組み合わせは、式を評価し、結果でnullを確認し、nullでない場合にのみコードを実行し、手動のnullチェックを回避する場合に役立ちます。

fun main() {

// Declares a list of nullable strings
val listWithNulls: List<String?> = listOf("Kotlin", null)

// Iterates over each item in the list
for (item in listWithNulls) {
// Checks if the item is null and only prints non-null values
item?.let { println(it) }
//Kotlin
}

}

Safe casts

type castsの通常のKotlin operatorは、as operatorです。ただし、オブジェクトがターゲット型でない場合、通常のキャストは例外になる可能性があります。

safe castsにはas? operatorを使用できます。これは、値を指定された型にキャストしようとし、値がその型でない場合はnullを返します。

fun main() {

// Declares a variable of type Any, which can hold any type of value
val a: Any = "Hello, Kotlin!"

// Safe casts to Int using the 'as?' operator
val aInt: Int? = a as? Int
// Safe casts to String using the 'as?' operator
val aString: String? = a as? String

println(aInt)
// null
println(aString)
// "Hello, Kotlin!"

}

aIntではないため、上記のコードはnullを出力し、キャストは安全に失敗します。また、String?型と一致するため、"Hello, Kotlin!"を出力し、safe castは成功します。

Collections of a nullable type

nullable elementsのコレクションがあり、null非許容のもののみを保持する場合は、filterNotNull()関数を使用します。

fun main() {

// Declares a list containing some null and non-null integer values
val nullableList: List<Int?> = listOf(1, 2, null, 4)

// Filters out null values, resulting in a list of non-null integers
val intList: List<Int> = nullableList.filterNotNull()

println(intList)
// [1, 2, 4]

}

What's next?