Найти тему

Руководство по блоку «when{}» в Kotlin

Оглавление

1. Обзор

В этом руководстве представлен блок When{} на языке Kotlin и показаны различные способы его использования.

2. Блок if{} в Котлине

Блок When{}, по сути, представляет собой расширенную форму оператора switch-case, известного из Java.

В Kotlin, если найден соответствующий вариант, выполняется только код в соответствующем блоке случая, и выполнение продолжается со следующего оператора после блока if .

По сути, это означает, что нам не нужны операторы прерывания в конце каждого блока case .

Чтобы продемонстрировать использование When{} , давайте определим класс перечисления, который будет содержать первую букву в поле разрешений для некоторых типов файлов в Unix:

enum class UnixFileType {
D, HYPHEN_MINUS, L
}

Давайте также определим иерархию классов, моделирующих соответствующие типы файлов Unix:

sealed class UnixFile {

abstract fun getFileType(): UnixFileType

class RegularFile(val content: String) : UnixFile() {
override fun getFileType(): UnixFileType {
return UnixFileType.HYPHEN_MINUS
}
}

class Directory(val children: List<UnixFile>) : UnixFile() {
override fun getFileType(): UnixFileType {
return UnixFileType.D
}
}

class SymbolicLink(val originalFile: UnixFile) : UnixFile() {
override fun getFileType(): UnixFileType {
return UnixFileType.L
}
}
}

2.1. When{} как выражение

Большим отличием от оператора switch в Java является то, что мы можем использовать блок When{} в Котлине и как оператор, и как выражение. Котлин следует принципам других функциональных языков. Структуры управления потоком представляют собой выражения, и результат их вычисления может быть возвращен вызывающей стороне.

Если возвращаемое значение присвоено переменной, компилятор проверит, что тип возвращаемого значения совместим с типом, ожидаемым клиентом, и сообщит нам, если это не так:

@Test
fun testWhenExpression() {
val directoryType = UnixFileType.D

val objectType = when (directoryType) {
UnixFileType.D -> "d"
UnixFileType.HYPHEN_MINUS -> "-"
UnixFileType.L -> "l"
}

assertEquals("d", objectType)
}

При использовании When в качестве выражения в Котлине следует обратить внимание на две вещи .

Во-первых, значение, возвращаемое вызывающей стороне, является значением соответствующего блока case или, другими словами, последним определенным значением в блоке.

Второе, на что следует обратить внимание, это то, что нам нужно гарантировать, что вызывающий объект получит значение. Чтобы это произошло, нам необходимо убедиться, что случаи в блоке When охватывают все возможные значения, которые можно присвоить аргументу.

2.2. When{} как выражение с регистром по умолчанию

Случай по умолчанию будет соответствовать любому значению аргумента, которое не соответствует обычному регистру, и в Котлине объявляется с использованием предложения else .

В любом случае компилятор Kotlin будет считать, что каждое возможное значение аргумента покрыто блоком if , и будет жаловаться, если это не так.

Вот как добавить регистр по умолчанию в выражение if Котлина:

@Test
fun testWhenExpressionWithDefaultCase() {
val fileType = UnixFileType.L

val result = when (fileType) {
UnixFileType.L -> "linking to another file"
else -> "not a link"
}

assertEquals("linking to another file", result)
}

2.3. Выражение if{} с случаем, вызывающим исключение

В Котлине throw возвращает значение типа Nothing .

В этом случае мы используем Nothing  , чтобы объявить, что выражению не удалось вычислить значение. Nothing — это тип, который наследуется от всех пользовательских и встроенных типов в Kotlin.

Следовательно, поскольку тип совместим с любым аргументом, который мы будем использовать в блоке if , вполне допустимо генерировать исключение из случая , даже если мы используем блок When в качестве выражения.

Давайте определим выражение When , в котором один из случаев вызывает исключение:

@Test(expected = IllegalArgumentException::class)
fun testWhenExpressionWithThrowException() {
val fileType = UnixFileType.L

val result: Boolean = when (fileType) {
UnixFileType.HYPHEN_MINUS -> true
else -> throw IllegalArgumentException("Wrong type of file")
}
}

2.4. когда {} используется как оператор

Мы также можем использовать блок When в качестве оператора.

В этом случае нам не нужно охватывать все возможные значения аргумента, а значение, вычисленное в каждом блоке case, если таковое имеется, просто игнорируется. В качестве оператора мы можем использовать блок if аналогично тому, как мы используем оператор switch в Java.

Давайте посмотрим на блок When как на оператор:

@Test
fun testWhenStatement() {
val fileType = UnixFileType.HYPHEN_MINUS

when (fileType) {
UnixFileType.HYPHEN_MINUS -> println("Regular file type")
UnixFileType.D -> println("Directory file type")
}
}

Мы видим, что не обязательно охватывать все возможные значения аргументов, когда мы используем if в качестве оператора.

2.5. Объединение случаев когда{}

Выражение if в Котлине позволяет нам объединять разные случаи в один, объединяя условия сопоставления запятой.

Для выполнения соответствующего блока кода должен совпадать только один регистр, поэтому запятая действует как оператор ИЛИ .

Давайте создадим случай, сочетающий в себе два условия:

@Test
fun testCaseCombination() {
val fileType = UnixFileType.D

val frequentFileType: Boolean = when (fileType) {
UnixFileType.HYPHEN_MINUS, UnixFileType.D -> true
else -> false
}

assertTrue(frequentFileType)
}

2.6. When{} используется без аргумента

Kotlin позволяет нам опускать значение аргумента в блоке When .

По сути, это превращается в простое выражение if-elseif , которое последовательно проверяет случаи и запускает блок кода для первого соответствующего случая. Если мы опустим аргумент в блоке When , выражения case должны оцениваться как true или false.

Давайте создадим блок if , который опускает аргумент:

@Test
fun testWhenWithoutArgument() {
val fileType = UnixFileType.L

val objectType = when {
fileType === UnixFileType.L -> "l"
fileType === UnixFileType.HYPHEN_MINUS -> "-"
fileType === UnixFileType.D -> "d"
else -> "unknown file type"
}

assertEquals("l", objectType)
}

2.7. Динамические регистровые выражения

В Java мы можем использовать оператор switch только с примитивами и их коробочными типами, перечислениями и классом String .

Напротив, Kotlin позволяет нам использовать блок if с любым встроенным или определяемым пользователем типом.

Кроме того, падежи не обязательно должны быть постоянными выражениями, как в Java. Случаи в Kotlin могут представлять собой динамические выражения, которые оцениваются во время выполнения. Например, случаи могут быть результатом функции, если тип возвращаемого значения функции совместим с типом аргумента блока When .

Давайте определим блок When с динамическими выражениями регистра:

@Test
fun testDynamicCaseExpression() {
val unixFile = UnixFile.SymbolicLink(UnixFile.RegularFile("Content"))

when {
unixFile.getFileType() == UnixFileType.D -> println("It's a directory!")
unixFile.getFileType() == UnixFileType.HYPHEN_MINUS -> println("It's a regular file!")
unixFile.getFileType() == UnixFileType.L -> println("It's a soft link!")
}
}

2.8. Выражения диапазона и коллекции Case

В блоке When можно определить регистр , который проверяет, содержит ли данная коллекция или диапазон значений аргумент.

По этой причине Котлин предоставляет оператор in , который является синтаксическим сахаром для метода contains() . Это означает , что Kotlin «за кулисами» преобразует элемент case в Collection.contains(element) .

Вот как проверить, находится ли аргумент в списке:

@Test
fun testCollectionCaseExpressions() {
val regularFile = UnixFile.RegularFile("Test Content")
val symbolicLink = UnixFile.SymbolicLink(regularFile)
val directory = UnixFile.Directory(listOf(regularFile, symbolicLink))

val isRegularFileInDirectory = when (regularFile) {
in directory.children -> true
else -> false
}

val isSymbolicLinkInDirectory = when {
symbolicLink in directory.children -> true
else -> false
}

assertTrue(isRegularFileInDirectory)
assertTrue(isSymbolicLinkInDirectory)
}

Теперь проверим, что аргумент находится в диапазоне:

@Test
fun testRangeCaseExpressions() {
val fileType = UnixFileType.HYPHEN_MINUS

val isCorrectType = when (fileType) {
in UnixFileType.D..UnixFileType.L -> true
else -> false
}

assertTrue(isCorrectType)
}

Несмотря на то, что тип REGULAR_FILE явно не содержится в диапазоне, его порядковый номер находится между порядковыми номерами DIRECTORY и SYMBOLIC_LINK , и поэтому тест считается успешным.

2.9. является оператором по работе с клиентами и Smart Cast

Мы можем использовать оператор is Kotlin  , чтобы проверить, является ли аргумент экземпляром указанного типа. Оператор is аналогичен оператору экземпляра в Java.

Однако Котлин предоставляет нам функцию под названием Smart Cast. После того как мы проверим, является ли аргумент экземпляром заданного типа, нам не нужно явно приводить аргумент к этому типу, поскольку компилятор делает это за нас.

Поэтому мы можем использовать методы и свойства, определенные в данном типе, непосредственно в блоке case.

Давайте воспользуемся оператором is с функцией интеллектуального приведения в блоке if:

@Test
fun testWhenWithIsOperatorWithSmartCase() {
val unixFile: UnixFile = UnixFile.RegularFile("Test Content")

val result = when (unixFile) {
is UnixFile.RegularFile -> unixFile.content
is UnixFile.Directory -> unixFile.children.map { it.getFileType() }.joinToString(", ")
is UnixFile.SymbolicLink -> unixFile.originalFile.getFileType()
}

assertEquals("Test Content", result)
}

Без явного приведения unixFile к RegularFile , Directory  или SymbolicLink мы смогли использовать RegularFile.content , Directory.children  и SymbolicLink.originalFile соответственно.

2.10. когда  выражения и циклы

Начиная с Kotlin 1.4, цикл  можно  прервать  или  продолжить  даже внутри выражения if:

val colors = setOf("Red", "Green", "Blue")
for (color in colors) {
when(color) {
"Red" -> break
"Green" -> continue
"Blue" -> println("This is blue")
}
}

Здесь Break  завершает ближайший охватывающий цикл, а continue  , как и ожидалось, переходит к следующему шагу.

Однако до Kotlin 1.4  в выражении if  внутри цикла допускались  только квалифицированные прерывания  и  продолжения:

LOOP@ for (color in colors) {
when(color) {
"Red" -> break@LOOP
"Green" -> continue@LOOP
"Blue" -> println("This is blue")
}
}

Как показано выше,  разрыв  и  продолжение  определяются выражением  @looplay.

3. Несколько операторов в блоке When{}

В этом разделе мы узнаем, как выполнять несколько операторов в блоке When{} для каждой из его ветвей. Для этой цели давайте представим себе случай использования, в котором мы хотим оценить, является ли данное целое число положительным, и вывести на экран самоочевидные утверждения, когда оно положительное, отрицательное или нулевое.

Во-первых, давайте продолжим и напишем функцию isPositiveInt() , которая принимает значение Int и возвращает логическое значение:

fun isPositiveInt(number: Int): Boolean {
val result = when (number) {
0 -> {
println("number is zero.")
print("It's neither positive nor negative.")
return false
}
in -1 downTo Int.MIN_VALUE -> {
print("number is negative")
return false
}
else -> {
print("number is positive")
return true
}
}
return result
}

Как мы видим, мы учли три разных интервала для целочисленного значения: положительный, отрицательный и нулевой. Кроме того, мы использовали фигурные скобки, чтобы добавить несколько операторов для каждой ветви.

Далее давайте подготовимся к тестированию функции isPositiveInt() , захватив стандартный поток вывода в экземпляр ByteArrayOutputStream:

val outputStream = ByteArrayOutputStream()
System.setOut(PrintStream(outputStream))

Наконец, давайте проверим наш метод для сценария, где заданное число равно нулю:

val givenNumber = 0
val expectedOutput = "number is zero.\nIt's neither positive nor negative."
val isPositive = isPositiveInt(givenNumber)
assertFalse(isPositive)
assertEquals(expectedOutput, outputStream.toString())

Большой! Это работает так, как и ожидалось для этого сценария. Аналогичным образом мы также можем проверить функцию isPositiveInt() на наличие положительных и отрицательных значений.

4. Вывод

В этой статье мы увидели несколько примеров использования блока When , предлагаемого языком Kotlin.

Несмотря на то, что в Kotlin невозможно выполнить сопоставление с образцом с помощью if , как в случае с соответствующими структурами в Scala и других языках JVM, блок When достаточно универсален, чтобы заставить нас полностью забыть об этих функциях.

Оригинал статьи: https://www.baeldung.com/kotlin/when