Kotlin 的类型化使 Java 或 Scala 无法实现的实现成为可能?



我最熟悉Java Type Erasure(其所有问题和好处)。我对Kotlin类型系统的扩展可能性有限,但是我对类型的重新化如何在面向擦除的JVM上起作用。什么是类型的重新化,Kotlin如何在JVM上成为可能,这与Java的类型Erasure和Scala的复杂类型系统有何不同?

什么是重新化?

类型重新化是科特林的技巧之一。如果将通用参数声明为 reified

由于它是内衬的,因此通用参数可以是具体的class,而不仅仅是编译时类型的信息。
您可以在Java中做一些不可能的事情:

实例

您现在可以使用instanceof S(在Kotlin,is s):

inline fun <reified T> f(a: Any) {
    if (a is T) println("Hi!")
}

这显然在Java中是不可能的。

反射

您现在可以从通用参数中获取Java java.lang.Class<T>实例。

inline fun <reified T> f(a: Any) {
    println("Hey! my class is ${T::class.java}!")
    if (a.javaClass == T::class.java) println("Hi!")
}

另外,KClass也:

inline fun <reified T> f(a: Any) {
    println("KClass: ${T::class}")
}

您可以使用空构造函数创建实例:

inline fun <reified T> f(a: Any) {
    val o: T = T::class.java.newInstance()
}

调用其他repified

只有reified通用参数可以传递给其他reified函数。

inline fun <reified T> f(a: Any) {
    g<T>(a)
}
inline fun <reified T> g(a: Any) {
    if (a is T) println("Bingo!")
}

Kotlin中这是不可能的:

inline fun <reified T> f(a: Any) {
}
fun <T> g(a: Any) {
    f<T>(a) // error
}

缺点(编辑)

如果您使用其他语言调用kotlin中的 reified内联函数,则该功能参数将为 java.lang.Object

您不能使用其他语言调用reified函数。

喜欢,如果我们在A.kt中具有REFIFIED功能:

inline fun <reified T> f(a: T) = println(T::class.java)

并使用反射(将作为私有编译)将其获取:

Method method = AKt.class.getDeclaredMethod("f", Object.class);

此代码将成功运行而无需例外。
但是您无法调用它(由于其实现,我没有仔细阅读生成的字节码,对不起):

private static final void f(Object a) {
  Intrinsics.reifiedOperationMarker(4, "T"); // I didn't see
  // the implementation of this line, so I thought it's
  // possible to call it in other languages
  Class var2 = Object.class;
  System.out.println(var2);
}

看评论。并查看reifiedOperationMarker的定义:

public static void reifiedOperationMarker(int id, String typeParameterIdentifier) {
    throwUndefinedForReified();
}

,它将抛出UnsupportedOperationException

结论:reified只能在Kotlin中使用。

关于Scala

真的很难说Kotlin还是Scala更好,因为Scala有更多在运行时获取类型信息的方法。

Alexey Romanov说Scala可以但Kotlin不能:

在递归功能中使用classtags

我认为可以通过在功能中使用函数来解决:

inline fun <reified T> g(a: Any): Int {
  var recur: ((Any) -> T)? = null
  recur = { recur!!.invoke(it) as T } // use T is possible here
  return recur(a)
}

请注意,这只是句法正确的示例。
当然,它是无限的循环和不必要的演员。

他还说:

将它们存储在集合中,并用它们来调用classtag-lusing unding函数。

这是一个真正的问题,因为这需要noinline lambdas,而Kotlin的reified基于内联。

当kotlin嵌入通用函数时,它自然会用它的类型代替类型参数。例如。使用inline fun <T> foo(x: T) = ...

foo(File("."))

变成

val x = File(".")
// body of foo with File used everywhere T was

reified所做的只是允许使用foo主体中的操作,该操作只有在此替代之后才有意义,但对于非reified类型参数是非法的,例如T::class

相关的Scala功能是ClassTag/TypeTag,而不是"复杂类型系统"。有效地,它可以自动通过Class<T>(或TypeToken<T>)作为一个参数,该参数可以在Java中手动进行,并且通常是。请注意,这完全是A 完全不同于reified

我认为reified在Scala中没有任何可能的事情,但是Kotlin方法的优势是更自然的语法:例如。在Scala中,您不能只在ClassTag中写classOf[T]-使用classOf[File]的方法。

otoh,scala允许reified无法做到的事情,例如:

  1. 在递归功能中使用ClassTag s

  2. 将它们存储在集合中并使用它们来调用ClassTag-稍后使用功能。

最新更新