Kotlin的类型具体化在Java或Scala中是不可能实现的?

问题描述 投票:6回答:2

我最熟悉Java类型擦除(包括所有问题和好处)。我对Kotlin类型系统的扩展可能性有一些有限的了解,但是我还没有清楚地了解类型验证如何在面向擦除的JVM上工作。什么是类型修改,Kotlin如何在JVM上实现,以及它与Java的类型擦除和Scala的复杂类型系统有何不同?

java scala generics types kotlin
2个回答
5
投票

什么是具体化?

类型具体化是Kotlin的技巧之一。如果将泛型参数声明为reified,则仅在内联泛型函数中发生。

由于它是内联的,泛型参数可以是具体的class,而不仅仅是编译时类型信息。 你可以在Java中做一些不可能的事情:

的instanceof

你现在可以使用instanceofs(在Kotlin,iss):

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

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

反射

您现在可以从generic参数获取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()
}

打电话给其他人

只有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!")
}

这在科特林是不可能的:

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

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

缺点(编辑)

如果您使用其他语言在Kotlin中调用reified内联函数,则函数参数将为java.lang.Object

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

就像,如果我们在A.kt中有一个具体的功能:

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

结论:reifeid只能用于Kotlin。

关于斯卡拉

很难说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-using函数。

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


3
投票

当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手动完成。请注意,这是一种与reified完全不同的方法。

我不认为reified做的任何事情在Scala中是不可能的,但Kotlin方法的优点是更自然的语法:例如在Scala你不能像classOf[T]那样用ClassTag使用方法写classOf[File]

OTOH,Scala允许用reified无法实现的东西,例如:

  1. 在递归函数中使用ClassTags
  2. 将它们存储在集合中并使用它们稍后调用ClassTag-using函数。
© www.soinside.com 2019 - 2024. All rights reserved.