kotlin的reified

2019-04-23 15:55:28 浏览数 (1)

对于我这种写惯了C 的人来说Java的泛型真的很难用。运行时没有类型信息,进一步导致像是jackson之类的库在做convertValue之类的操作时,方法虽然是个泛型方法,但是还得带上一个Class<T>的参数才能做转换。

自从有了kotlin,一切都不一样了。

jackson-kotlin-module提供了基于reified的简化版本

代码语言:txt复制
inline fun <reified T> ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef<T>())

对比Java的版本

代码语言:txt复制
public <T> T convertValue(Object fromValue, TypeReference<?> toValueTypeRef)

少了个参数简直爽爆了

实现

那这具体是怎么实现的呢?

我们知道Java的泛型只是编译时做的参数检查,运行时并没有保留任何信息,任何泛型类/方法也是普通的类/方法。

kotlin也是基于Jvm平台的,那kotlin中是如何实现泛型参数传递的呢。我们可以通过反编译字节码一窥究竟。

`kotlin` fun main() { jacksonObjectMapper().readValue<String>("") }inline fun <reified T> ObjectMapper.readValue(content: String): T = readValue(content, jacksonTypeRef<T>()) inline fun <reified T> jacksonTypeRef(): TypeReference<T> = object: TypeReference<T>() {}

上面kotlin代码编译后的字节码可以反编译可以得到类似下面Java代码

代码语言:txt复制
public final class AppKt$main$$inlined$readValue$1 extends TypeReference<String>
{
}
public final class AppKt
{
  public static final void main()
  {
    ObjectMapper localObjectMapper1 = ExtensionsKt.jacksonObjectMapper();
    String content$iv = "";
    int $i$f$readValue;
    ObjectMapper $receiver$iv;
    String str1 = content$iv;
    ObjectMapper localObjectMapper2 = $receiver$iv;
    int $i$f$jacksonTypeRef;
    TypeReference localTypeReference = (TypeReference)new AppKt.main..inlined.readValue.1(); localObjectMapper2.readValue(
      str1, localTypeReference);
  }
}

可以看到由于inline的关系kotlin的readValue倍直接展开到main函数中

另外jacksonTypeRef<T>被转换换成AppKt$main$$inlined$readValue$1类型直接包含了泛型参数String

看起来挺简单的就是直接展开代码嘛,那和C 一样泛型满天飞不就行了么。

限制

其实即便kotlin用inline实现了泛型代码运行时携带泛型信息,也没有达到C 模板展开的层次。具体来说就是下面这个例子

代码语言:txt复制
data class Wrapper<T>(val data:T)
data class A(val a: Int)

inline fun <reified T> convert(a: String): Wrapper<T> {
    return jacksonObjectMapper().readValue(a)
}

fun main() {
    println(convert<A>("""{"data":{"a":1}}"""))
    println(jacksonObjectMapper().readValue<Wrapper<A>>("""{"data":{"a":1}}"""))
}

输出

代码语言:txt复制
Wrapper(data={a=1})
Wrapper(data=A(a=1))

可以看到convert方法没有把data字段转换成类A,而是Map

回头看一下反编译的Java代码就很清晰了

代码语言:txt复制
public final class AppKt
{
  private static final <T> Wrapper<T> convert(String a)
  {
    int $i$f$convert = 0; ObjectMapper $receiver$iv = ExtensionsKt.jacksonObjectMapper();
    int $i$f$readValue;
    String str = a; ObjectMapper localObjectMapper1 = $receiver$iv;
    int $i$f$jacksonTypeRef;
    TypeReference localTypeReference = (TypeReference)new AppKt.convert..inlined.readValue.1();

    return (Wrapper)
      localObjectMapper1.readValue(
      str, localTypeReference);
  }

  public static final void main()
  {
    String a$iv = "{"data":{"a":1}}"; int $i$f$convert = 0;

    ObjectMapper $receiver$iv$iv = ExtensionsKt.jacksonObjectMapper();
    int $i$f$readValue;
    Object localObject1 = a$iv; Object localObject2 = $receiver$iv$iv;
    int $i$f$jacksonTypeRef;
    TypeReference localTypeReference = (TypeReference)new AppKt.convert..inlined.readValue.2();

    a$iv = (Wrapper)
      ((ObjectMapper)localObject2).readValue(
      (String)localObject1, localTypeReference);

    System.out.println(a$iv);
    a$iv = ExtensionsKt.jacksonObjectMapper(); String content$iv = "{"data":{"a":1}}";
    int $i$f$readValue;
    $receiver$iv$iv = content$iv; localObject1 = $receiver$iv;
    int $i$f$jacksonTypeRef;
    localObject2 = (TypeReference)new AppKt.main..inlined.readValue.1();

    ObjectMapper $receiver$iv = 
      ((ObjectMapper)localObject1).readValue(
      $receiver$iv$iv, (TypeReference)localObject2);

    System.out.println($receiver$iv);
  }
}

public final class AppKt$convert$$inlined$readValue$1 extends TypeReference<Wrapper<T>>
{
}
public final class AppKt$convert$$inlined$readValue$2 extends TypeReference<Wrapper<T>>
{
}
public final class AppKt$main$$inlined$readValue$1 extends TypeReference<Wrapper<A>>
{
}

对于convert方法使用的AppKt$convert$$inlined$readValue$2没有包含A类型,因此转换的时候并不会转换成A,而是普通的Map

那为什么会这样呢,我们可以看到这里的convert方法是inline的因此,main中调用的地方并不会直接调用convert方法,而是将convert方法的代码在main中展开

因为convert的代码并不知道T是什么类型,因此生成的中间类型也没有具体的参数

那为什么直接调用jackson的extesion有效呢?

总结起来就是一句话,inline reified方法的泛型参数必须就地使用,不能传递给别的inline reified方法。

0 人点赞