当前位置: 首页 > 知识库问答 >
问题:

为什么在 Kotlin 中使用了经过修饰的关键字,内联标记函数还不够吗?

方玄天
2023-03-14

在 Kotlin 中,鉴于“reified”关键字只能用于内联函数的泛型类型参数,为什么还要使用 reified 关键字呢?为什么 Kotlin 编译器(至少在将来)不能自动将内联函数的所有泛型类型参数视为经过修改?

我看到人们在看到这个“具体化”的词时感到恐慌,并要求我不要让代码变得复杂。因此产生了这个问题。

共有3个答案

从劲
2023-03-14

接受@михаилнафталь的回答(第一次提供并进一步更新),同时也热烈感谢@Tenfour04。我只是想根据我对所提供答案的理解,添加一个(希望如此)更简单的答案,这在本质上仍然是正确的:

  • “可再生类型”的实用(但可能不完整)定义是,如果类型类似于List

康恩
2023-03-14

正如@Гиаии1072;ифбцкПви1072Яю的回答所表明的,被具体化的类型是非常有限的,因此语言要求您明确哪些类型应该具体化是至关重要的。具体化要求类型在编译时已知,因此具有具体化类型的函数只能从类型不是非具体化泛型类型的函数中调用。

有人可能会说,那么,只有当< code>T::class碰巧用在这个内联函数中时,才假设它是具体化的,否则就将其视为未具体化。但是这意味着你的有效函数签名可以通过改变函数的内容而不改变它的声明来改变。这将使得意外更改函数签名变得非常容易,这是未来灾难的一个原因。例如,假设我有这个函数:

inline fun <T> registerList(list: List<T>) { // T is not reified
    myProtectedRegistryList.add(list)
}

所以它被用在我的应用程序的其他地方,或者被我的库的用户这样使用:

class Foo<T>(val data: List<T>) {
    init { 
        registerList(data)
    }
}

// or

fun foo(data: List<T>) {
    registerList(data)
}

// or

class Bar<T> {
    var barRegister: (List<T>)->Unit = ::registerList
}

后来我在不更改其声明的情况下修改了我的函数:

// In hypothetical Kotlin with implicit reification, T is now reified:
inline fun <T> registerList(list: List<T>) { // This line of code unchanged!
    myProtectedRegistryMap.put(T::class, list)
}

现在,我已经破坏了它在任何地方使用的代码,就像上面的例子一样。所以,由于语言要求你改变声明来改变签名,你被迫考虑修改函数的外部影响。你知道,如果你重构任何函数的声明,这是它在调用站点的可用性受到影响的唯一方式。

Kotlin在这类问题上的设计哲学是保守的,需要明确的声明。这与函数接口必须用< code>fun关键字显式标记的原因相同,即使它们当前只有一个抽象函数,并且类/函数在默认情况下是final。

贡威
2023-03-14

具体化的类型参数要求传递给它们的类型参数也要具体化。有时这是一个不可能的要求(例如,类参数不能被具体化),因此,在默认情况下使内联函数的所有参数具体化将使调用所有内联函数变得不可能,而现在只能调用具有具体化类型参数的函数:

inline fun<T> genericFun(x: T)  {}
inline fun<reified T> reifiedGenericFun(x: T)  {}

class SimpleGenericClass<T>() {
    fun f(x: T) {
        genericFun<T>(x)        //compiles fine
        reifiedGenericFun<T>(x) //compilation error
    }
}

更新。为什么不根据上下文自动推断“可靠性”?

  1. 方法1(由@Tenfour04建议):分析内联函数的代码,如果它有<code>T::class
  2. 方法2(由@SillyQuestion建议):将内联函数的所有类型参数视为默认具体化;如果它导致使用站点上的编译错误,则回退到非具体化类型

下面是两者的反例:< code >“a”as?T。具有此主体的函数将具有不同的语义,这取决于其类型参数是否被声明(或者,假设,推断)为具体化:

inline fun<reified T> castToReifiedGenericType() = "a" as? T
inline fun<T> castToSimpleGenericType() = "a" as? T

fun main() {
    println(castToReifiedGenericType<Int>()) //null
    println(castToSimpleGenericType<Int>())  //a
}

/*P.S. unsafe cast ("a" as T) also have different semantics for reified and non-reified T, 
causing `ClassCastException` in the first case and still returning "a" in the latter.*/

因此,对于第一种方法,如果我们添加一个无意义的调用到T,语义学会改变::class/是T在内联函数中的某个位置。对于第二种-如果我们从新站点调用这个函数(其中T不能被具体化,而它之前是“reiable”),或者,相反,从该站点删除一个调用(允许它被具体化),语义学会改变。

调试来自这些操作的问题(乍一看与观察语义变化无关)比添加/读取显式的reified关键字要复杂得多,并且会引起恐慌。

 类似资料:
  • 问题内容: 为什么在Java中不使用const关键字? 您能看到在Java语法中使用某些可传递const或不可变关键字的任何缺点,还是为什么选择了常见的繁琐方法? 您能看到关闭请求的 原因 ,Sun提供了任何解释吗? 问题答案: 您能看到关闭请求的原因吗,Sun是否提供任何解释? 是。Sun在请求本身中提供了三个为什么不对请求执行操作的原因。我引用: “目前尚无计划将此功能添加到Java中。除了蔓

  • 问题内容: 为什么没有用于同步/并发的关键字? 到目前为止,我的研究为我提供了一种解决方案,您包装了一些高级类并使用它们来处理并发。 给定一个纯Kotlin项目,如果需要一个小型的高度优化的组件来处理并发等,该怎么办? 我的印象是Kotlin是Java的辅助语言,可以用Kotlin编写90%的代码,但有一些Java语言无法用Kotlin表示。 这是正确的吗?这是原来的样子吗? 问题答案: Kotl

  • 为什么没有同步和并发的关键字? 到目前为止,我的研究给了我一个解决方案,你包装一些高级类,并用它们来处理并发。 给定一个纯Kotlin的项目,如果需要一个以线程安全方式处理并发的小型、高度优化的组件,应该怎么做? 我的印象是,Kotlin是Java的一种辅助语言,可以用Kotlin编写90%的代码,但有一些Java代码无法用Kotling表达。 这是对的吗?这就是它的目的吗?

  • 这样对吗?这就是它的初衷吗?

  • 修饰类方法的关键字 通过在func关键字之前使用static关键字来定义一个类型方法. 类同样可以使用class关键字来定义一个类型方法, 区别在于class修饰的方法, 允许子类重写 e.g. static static func talk() -> Void { print("\(self) talk method") } class class Pe

  • 我仍然在考虑Kotlin中的coroutines,但我想知道这是否对我的问题有些矫枉过正,即在返回数据时立即更新文本视图。异常处理使回调变得非常丑陋,我想尽可能避免这些 suspend关键字是否仅仅意味着运行库可能会挂起一个需要一段时间才能完成的函数?还是只在coroutine内启用挂起?作为一个假设,我能写吗 是否知道,如果下载了1 GB的数据,将同时调用,而则等待由填充?