首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >kotlin的reified

kotlin的reified

原创
作者头像
王沛文
发布2019-04-23 15:55:28
1.3K0
发布2019-04-23 15:55:28
举报
文章被收录于专栏:王沛文的专栏王沛文的专栏

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

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

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

inline fun <reified T> ObjectMapper.convertValue(from: Any): T = convertValue(from, jacksonTypeRef<T>())

对比Java的版本

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代码

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++模板展开的层次。具体来说就是下面这个例子

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}}"""))
}

输出

Wrapper(data={a=1})
Wrapper(data=A(a=1))

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

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

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方法。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现
  • 限制
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档