【问题标题】:What can be expressed in a compile-time constant (const val)?编译时常量(const val)可以表达什么?
【发布时间】:2017-07-05 09:08:52
【问题描述】:

编译时常量的文档列出了属性需要满足的三个要求,才能将其声明为const val。它们是:

  • 对象的顶级或成员
  • 使用 String 类型或原始类型的值初始化
  • 没有自定义 getter

“无自定义 getter”要求让我相信我不能在常量声明中使用任何函数,但事实似乎并非如此。这些编译:

const val bitmask = (5 shl 3) + 2
const val aComputedString = "Hello ${0x57.toChar()}orld${((1 shl 5) or 1).toChar()}"
const val comparedInt = 5.compareTo(6)
const val comparedString = "Hello".compareTo("World!")
const val toStringedInt = 5.compareTo(6).toString()
const val charFromString = "Hello World!".get(3)

但是,这些不会编译:

// An extension function on Int.
const val coercedInt = 3.coerceIn(1..5)

// Using operator syntax to call the get-function.
const val charFromString = "Hello World!"[3]

// An immediate type is not a primitive.
const val stringFromImmediateList = "Hello World!".toList().toString()

// Using a function defined by yourself.
fun foo() = "Hello world!"
const val stringFromFunction = foo()

编译时常量的确切规则是什么?

是否有我可以在编译时常量声明中使用的函数列表?

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    对此没有确切的文档,但可以在编译器源代码here 中找到可以在常量表达式中使用的函数列表。请注意,只有那些函数可以在 kotlin 包下定义的常量表达式中使用,在自定义重载运算符时编译器会报告错误。

    【讨论】:

    • 那么这些函数除了在这个文件中之外没有共同点?
    • 链接已断开,OperationsMapGenerated.kt 似乎不再存在。在当前版本中,编译时常量的代码在ConstantExpressionEvaluator.kt。
    【解决方案2】:

    getter不是方法调用,确实是属性声明的一部分,比如下面的代码不能编译。

    const val charFromString get() = "foo"
    //                       ^--- const using getter can't be compiled
    

    aComputedString 常量使用字符串模板,就像 java 中的字符串连接一样,例如:

    static final String aComputedString = "Hello " + ((char) 0x57) 
                                        + "orld" + ((char) ((1 << 5) | 1));
    

    并且操作符针对原始类型进行了优化,因为它们在 java 中没有方法,例如:

    const val longValue = 1.toLong();
    // java
    static final long longValue = (long) 1 ;
    

    comparedString 上面的代码可以工作是你使用kotlin.String 而不是java.lang.String,因为映射类型kotlin.String 也被优化,因为在 kotlin 中没有实现,如果你尝试java.lang.String直接可以得到预期的编译错误:

    typealias JavaString = java.lang.String;
    //         v--- error
    const val comparedString = JavaString("Hello").compareTo("World!")
    

    "Hello world!"[3] 不能工作,因为indexed access operator 的参数类型是vararg,所以编译器无法优化它,因为它不知道get 运算符的参数有多少被接收,因此它会使用List<KtExpression> 列表动态调用,例如:

    const val third  = "Hello world!"[3] //error
    // will generate java code as 
    static final String third  = "Hello world!".charAt(3) // error
    

    但是对于基本类型的固定参数的操作符,编译器会优化:

    请注意,这些操作以及所有其他操作均针对基本类型优化,并且不会为它们引入函数调用开销。

    const val comparison = "Hello" > "World";// ok
    

    String.get(n) 可以工作,因为kotlin.String 是一个映射器类型,它没有实现,所以编译器知道如何计算它,例如:

     const val third = "Hello".get(3) // ok
     //                         ^
     // when you calling the `get` function, you never using `operator` at all.
     //  it just a function invocation
    

    String.toList() 不能分配给常量变量,因为它是一种扩展方法并且有实现。而 kotlin const val 只支持 primitive types 和 strings.

    【讨论】:

    • 这是真的,但这并不能解释为什么我可以在const val-declaration 中使用某些函数,而不能使用其他函数。
    • 很多这些函数不是操作符函数。 comparedInt 和 comparedString 呢?为什么"Hello world!"[3] 不起作用?这似乎不是编译时常量的确切规则。
    • @marstran 嗨,也许这就是你想要的。
    • 你提到的操作符重载页面告诉我a[i]被翻译成a.get(i)。我找不到任何说它被翻译成charAt-method。
    • 当然,但这仍然不能解释为什么a[i] 和a.get(i) 之间存在差异,因为文档清楚地说明第一个将被翻译成另一个。看起来像一个编译器错误恕我直言。
    猜你喜欢
    • 2019-02-01
    • 2017-02-25
    • 1970-01-01
    • 2014-04-20
    • 2019-03-14
    • 2013-10-16
    • 1970-01-01
    • 2010-09-14
    • 2020-11-24
    相关资源
    最近更新 更多