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