【问题标题】:Why is a Kotlin enum property not a compile time constant?为什么 Kotlin 枚举属性不是编译时间常数?
【发布时间】:2022-01-25 01:32:47
【问题描述】:

这个问题的主要目标是了解实现以及为什么会这样。当然,它的解决方案或解决方法也将受到高度赞赏......

举个例子:

enum class SomeEnum(val customProp: String) {
  FOO("fooProp"),
  BAR("barProp");
}

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class TheAnnotation(
  val targetValue: String
)

@TheAnnotation(targetValue = SomeEnum.FOO.customProp)
fun testFun() {
  
}

编译导致如下错误:

SomeEnum.kt: (14, 30): 注解参数必须是编译时常量

由于显而易见的原因,注释值(以及其他值)必须是编译时常量,这在许多不同方面都有意义。我不清楚的是,为什么 customProp 不被编译器视为常量。

如果枚举被定义为有限的、封闭的信息集,在我的理解中,它们应该只在编译时可变,也就是“编译时常量”。对于在 Kotlin 中以某种方式在运行时可以修改枚举的不太可能的情况,这也可以回答这个问题。

附录:

枚举值(例如SomeEnum.FOO)实际上被视为编译时常量。证明是,以下稍微改变的 sn-p 编译:

enum class SomeEnum(val customProp: String) {
  FOO("fooProp"),
  BAR("barProp");
}

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class TheAnnotation(
  val targetValue: SomeEnum
)

@TheAnnotation(targetValue = SomeEnum.FOO)
fun testFun() {

}

【问题讨论】:

  • 合理怀疑可能是Foo(if System.currentTimeMillis() % 2L == 0L) "hello" else "bye")。我还没有尝试过,因为我现在在手机上,但如果编译成功,那么你就有答案了

标签: kotlin enums


【解决方案1】:

枚举被定义为有限的、封闭的信息集,据我所知,它们应该只在编译时可变

实际上,没有。枚举类只是一种特殊的类,除了您在声明中命名的实例之外,它不允许您创建任何新实例,以及更多的语法糖。因此,与常规类一样,它可以具有仅在运行时才知道值的属性,以及可变的属性(尽管这通常是一个非常糟糕的主意)。

例如:

enum class Foo {
    A, B;

    val foo = System.currentTimeMillis() // you can't know this at compile time!
}

这基本上会变成:

class Foo private constructor(){
    val foo = System.currentTimeMillis()
    companion object {
        val A = Foo()
        val B = Foo()
    }
}

(实际生成的代码比这有点多,但这足以说明我的观点)

AB 只是Foo 的两个(只有两个)实例。应该很明显Foo.A不是编译时常量*,更不用说Foo.A.foo了。您可以在Foo 中添加一个init 块来运行任意代码。你甚至可以把foo 变成var,让你做一些可怕的事情,比如:

Foo.A.foo = 1
// now every other code that uses Foo.A.foo will see "1" as its value

您可能还想知道为什么他们没有实现一个不允许您做这些事情的更受限制的枚举,并且 一个编译时间常数,而是that is a different question

另见:The language spec


* 虽然您仍然可以将Foo.A 传递给注释。对于注解,Foo.A 是一个编译时常量,因为注解所要做的就是存储名称“Foo.A”,而不是它所引用的对象,该对象必须在运行时计算。

【讨论】:

  • 好点,而您的陈述“显然 Foo.A 不是编译时间常数”实际上是错误的。 Foo.A 是一个编译时常量,就像每个枚举一样,但属性 Foo.A.foo 不是,因为你是对的,它/可能是可变的。
  • @frank_neff 对象不能是编译时常量,因为它们是在运行时构造的,使用内部可能有任意逻辑的构造函数。编译器只能知道存在Foo.A 对象,但无法知道它到底是什么。所以这更像是一个单例而不是常量。
  • Foo.A 也不是编译时间常数。请参阅规范的this section。可以用作注解参数是一种特殊情况。
  • @frank_neff 假设Foo.A 是一个常量表达式,那么根据我上面链接的规则,"${Foo.A}" 是一个常量表达式。这意味着我们可以将"${Foo.A}" 传递给一个接受字符串的注解。但是,这是不可能的。
  • @frank_neff 这不是详尽无遗的时间。常量表达式恰好在该部分中指定。但是你确实有一个观点——我们一直在讨论两种常量表达式。 Broot 和我都提到了规范中详尽的 when 部分中指定的类型,但您似乎对可以传递给注解的表达式更感兴趣,编译器也将其称为“编译时常量”,使得这令人困惑。
猜你喜欢
  • 1970-01-01
  • 2012-07-31
  • 1970-01-01
  • 1970-01-01
  • 2017-12-05
  • 1970-01-01
  • 2020-05-06
  • 1970-01-01
相关资源
最近更新 更多