使用密封类强制编译错误

时间:2016-07-03 12:26:33

标签: kotlin

使用密封类,您可以使用详尽的when表达式,并在表达式返回结果时省略else子句:

sealed class SealedClass {
  class First : SealedClass()
  class Second : SealedClass()
}

fun test(sealedClass: SealedClass) : String =
    when (sealedClass) {
      is SealedClass.First -> "First"
      is SealedClass.Second -> "Second"
    }

现在,如果我要向Third添加SealedClass,编译器会抱怨when中的test()表达式并非详尽无遗,我需要添加一个Thirdelse的条款。

我想知道,当test()没有返回任何内容时,是否也可以强制执行此检查:

fun test(sealedClass: SealedClass) {
    when (sealedClass) {
      is SealedClass.First -> doSomething()
      is SealedClass.Second -> doSomethingElse()
    }
}

如果添加Third,则此代码段不会中断。 我可以在return之前添加when语句,但这很容易被遗忘,如果其中一个子句的返回类型不是Unit,则可能会中断。

如何确保我不会忘记在我的when条款中添加分支?

6 个答案:

答案 0 :(得分:21)

强制执行详尽when的方法是使用其值使其成为表达式:

sealed class SealedClass {
    class First : SealedClass()
    class Second : SealedClass()
    class Third : SealedClass()
}

fun test(sealedClass: SealedClass) {
    val x = when (sealedClass) {
        is SealedClass.First -> doSomething()
        is SealedClass.Second -> doSomethingElse()
    }  // ERROR here

    // or

    when (sealedClass) {
        is SealedClass.First -> doSomething()
        is SealedClass.Second -> doSomethingElse()
    }.let {}  // ERROR here
}

答案 1 :(得分:19)

在Voddan的回答中,您可以构建一个名为function init(){ var button = window.document.createElement("button"); var textNode = window.document.createTextNode("click me"); button.appendChild(textNode); window.document.body.appendChild(button); } 的属性,您可以使用:

safe

使用:

val Any?.safe get() = Unit

我认为它提供的信息比仅附加when (sealedClass) { is SealedClass.First -> doSomething() is SealedClass.Second -> doSomethingElse() }.safe 或将结果分配给值更清晰。

Kotlin追踪器上有一个open issue,它会考虑支持“密封的小孩”。

答案 2 :(得分:14)

我们的方法避免在自动完成时将功能放在任何地方。 使用此解决方案,您还可以在编译时使用when返回类型,以便继续使用when返回类型的函数。

Do exhaustive when (sealedClass) {
  is SealedClass.First -> doSomething()
  is SealedClass.Second -> doSomethingElse()
}

您可以像这样定义此对象:

object Do {
    inline infix fun<reified T> exhaustive(any: T?) = any
}

答案 3 :(得分:5)

我们可以在T类型上创建一个扩展属性,其名称有助于说明其目的

val <T> T.exhaustive: T
    get() = this

然后在类似

的任何地方使用它
when (sealedClass) {
        is SealedClass.First -> doSomething()
        is SealedClass.Second -> doSomethingElse()
    }.exhaustive

这是可读的,准确显示其操作,如果未涵盖所有情况,将显示错误。 Read more here

答案 4 :(得分:1)

一个discussion促使我寻找一种更通用的解决方案,并为Gradle构建找到了一个解决方案。 不需要更改源代码!缺点是编译可能会变得嘈杂。

build.gradle.kts

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    val taskOutput = StringBuilder()
    logging.level = LogLevel.INFO
    logging.addStandardOutputListener { taskOutput.append(it) }
    doLast {
        fun CharSequence.hasInfoWithError(): Boolean =
            "'when' expression on sealed classes is recommended to be exhaustive" in this
        if (taskOutput.hasInfoWithError()) {
            throw Exception("kotlinc infos considered as errors found, see compiler output for details.")
        }
    }
}

build.gradle

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
    def taskOutput = new StringBuilder()
    logging.level = LogLevel.INFO
    logging.addStandardOutputListener(new StandardOutputListener() {
        void onOutput(CharSequence text) { taskOutput.append(text) }
    })
    doLast {
        def hasInfoWithError = { CharSequence output ->
            output.contains("'when' expression on sealed classes is recommended to be exhaustive")
        }
        if (hasInfoWithError(taskOutput)) {
            throw new Exception("kotlinc infos considered as errors found, see compiler output for details.")
        }
    }
}

注意:

  • 更改hasInfoWithError的实现以推广到其他i:
  • 将此代码放入subprojects { }allprojects { }中以在整个项目中应用。

参考:

答案 5 :(得分:1)

考虑使用JakeWharton的最新库,该库允许仅使用@Exhaustive注释。

sealed class RouletteColor {
  object Red : RouletteColor()
  object Black : RouletteColor()
  object Green : RouletteColor()
}

fun printColor(color: RouletteColor) {
  @Exhaustive
  when (color) {
    RouletteColor.Red -> println("red")
    RouletteColor.Black -> println("black")
  }
}

用法:

buildscript {
  dependencies {
    classpath 'app.cash.exhaustive:exhaustive-gradle:0.1.1'
  }
  repositories {
    mavenCentral()
  }
}

apply plugin: 'org.jetbrains.kotlin.jvm' // or .android or .multiplatform or .js
apply plugin: 'app.cash.exhaustive'

库:https://github.com/cashapp/exhaustive