Руководство по блоку «когда \ {}» в Котлине

Руководство по блоку «когда \ {}» в Котлине

1. Вступление

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

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

2. Блокwhen\{} Котлина

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

В Kotlin, если соответствующий регистр найден, то выполняется только код в соответствующем блоке case, и выполнение продолжается со следующего оператора после блокаwhen. По сути, это означает, что в конце каждого блокаcase не требуется никаких операторов break.

Чтобы продемонстрировать использование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() {
        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 состоит в том, чтоthe when\{} block in Kotlin can be used both as a statement and as an expression. Kotlin следует принципам других функциональных языков, а структуры управления потоком представляют собой выражения, и результат их оценки может быть возвращен вызывающей стороне.

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

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

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

    assertEquals("d", objectType)
}

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

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

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

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

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

Чтобы добавить регистр по умолчанию в выражение Kotlinwhen:

@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. When\{} Expression with a Case That Throws an Exception

В Kotlinthrow возвращает значение типаNothing.

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

Следовательно, поскольку тип совместим с любым аргументом, который мы использовали бы в блокеwhen, вполне допустимо генерировать исключение изcase, даже если блок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\{} используется как утверждение

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

В этом случае нам не нужно указывать все возможные значения для аргумента, а значение, вычисленное в каждом блоке, если оно есть, просто игнорируется. При использовании в качестве оператора блокwhen можно использовать аналогично тому, как оператор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")
    }
}

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

2.5. Объединение случаевWhen\{}

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

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

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

@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.

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

@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 allows us to use the when block with any built-in or user defined type.

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

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

@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. Выражения Range и Collection Case

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

По этой причине Kotlin предоставляет операторin, который является синтаксическим сахаром для методаcontains(). Это означает, что Kotlin за кулисами переводит элемент casein в 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. Is Case Operator и Smart Cast

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

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

Следовательно, мы можем использовать методы и свойства, определенные в данном типе, непосредственно в блоке case. Чтобы использовать оператор is с функцией «умного приведения» в блокеwhen:

@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 соответственно.

3. Заключение

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

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

Полную реализацию примеров для этой статьи можно найти вover on GitHub.