【问题标题】:how does the method infer the type of <T>该方法如何推断 <T> 的类型
【发布时间】:2014-03-09 08:29:17
【问题描述】:

以下方法完美运行

public <T> void fromJsonArray(String jsonString,Type tToken) {
  Gson g = new Gson();
  T list = g.fromJson(jsonString,tToken);
  System.out.println(list);
}

但是我没有具体说明这个方法中的是什么。编译器如何将fromJson方法返回的值赋给我没有指定类型的变量list

我刚刚测试了关于 &lt;T&gt; 从方法的返回类型推断出来的答案的有效性。它似乎没有成功。请查看以下代码。它甚至没有编译

import java.util.*;

class Sample {

  public List<String> getT(String s) {
    List<String>  list = new ArrayList<String>();
    list.add(s);
    return list;
  }

  public <T> void test(){
    T list = getT("test");
    System.out.println(l);
  }

  public static void main(String[] a) {
    new Sample().test();
  }
}

再次修改源码并测试,结果编译时出错

public <T> List<T> getT(T s) {
  List<T>  list = new ArrayList<T>();
  list.add(s);
  return list;
}

public <T> void test(){
  T list = getT("test"); //incompatible types compilation error here
  System.out.println(list);
}

Sample1.java:13: error: incompatible types T list = getT("test"); ^ required: T found: List where T is a type-variable: T extends Object declared in method test()

【问题讨论】:

  • 也许它内联了变量,因为它在这里没用
  • 类型变量可以是您指定的任何非原始类型:任何类类型、任何接口类型、任何数组类型。
  • 这是一个很好的问题。我看不出如何从论点中推断出来。 Type 是一个非泛型接口,此时没有方法。我想知道编译器是否能够根据返回类型进行推断。用于类型推断的 JLS 很难理解。我个人不知道答案。
  • @BlackPanther。好的,您如何调用该方法? fromJsonArray?
  • @RohitJain 因为它是 void 我想知道他们是否在没有类型参数的情况下调用它(然后 Object 最终被推断为完整的调用......)。

标签: java generics gson


【解决方案1】:

方法如何推断&lt;T&gt;的类型

它没有。泛型方法不会推断它们的泛型类型——这就是为什么T 被称为类型参数。该方法的调用者T 提供了一个类型参数。当它这样做时,它可能由编译器根据方法调用的参数和目标类型的上下文来推断。

例如:

Set<String> c = Collections.emptySet();

emptySet 声明了一个类型参数T,不接受任何参数,并返回一个Set&lt;T&gt;。在这里,编译器根据目标类型Set&lt;String&gt; 推断TString

另一个例子:

Collections.singleton("asdf");

singleton 声明一个类型参数T,接受一个T,并返回一个Set&lt;T&gt;。这里没有目标类型,但编译器根据参数"asdf" 推断TString

但泛型类型推断只是一种方便。没有它,我们仍然可以使用 type witnesses 来显式提供类型参数:

Set<String> c = Collections.<String>emptySet();
Collections.<String>singleton("asdf");

这将我们带到您的方法签名:

public <T> void fromJsonArray(String jsonString, Type tToken)

fromJsonArray 声明了一个类型参数T,但不返回与T 类型相关的任何内容或获取与T 相关的参数。在调用fromJsonArray 时,编译器没有可从中推断T 的信息。除非使用类型见证,否则其类型参数将默认为其上限 Object

myObj.<String>fromJsonArray(jsonString, tToken);

但这并不重要,因为&lt;String&gt; 对方法调用的行为或其编译没有影响。 T 是无意义的*,可以从fromJsonArray 的声明中去掉。

编译器如何将fromJson方法返回的值赋给我没有指定类型的变量list

这里是Gson.fromJson(String, Type)的来源:

@SuppressWarnings("unchecked")
public <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
    StringReader reader = new StringReader(json);
    T target = (T) fromJson(reader, typeOfT);
    return target;
}

您可以看到它声明了一个任意类型参数T,并将反序列化的对象转换为T。这被称为未经检查的强制转换,因为如果它是错误的,它不会很快失败。那是因为T 在运行时一直是erased。您可以看到代码正在禁止执行此操作的警告,因为这通常是一个坏主意。通过不限制 T 基于方法参数的内容,Gson 代码有效地将其控制权交给了调用者。如果你写:

List<String> list = g.fromJson(jsonString, tToken);

tToken 代表HashSet&lt;String&gt;,您会在运行时在该行上获得ClassCastException。更糟糕的是,如果tToken 代表ArrayList&lt;Integer&gt;,它甚至不会在该行失败,因为JVM 只会看到List 并允许分配发生。 ClassCastException 将在稍后的某个时间抛出,一旦您的代码尝试像 Strings 一样处理列表的 Integer 元素(并且该异常会使调试感到困惑)。

所以为了回答您关于分配的问题,编译器允许您将fromJson 的结果分配给您想要的任何东西。是否正确取决于您。

您可能会问,为什么 Gson 会进行未经检查的强制转换并允许不安全的代码? 答案是出于语言限制,这是为了方便。他们的另一个签名更安全:

public <T> T fromJson(String json, Class<T> classOfT)

但是没有办法用Class 表示泛型类型——例如没有List&lt;String&gt;.class。只有Type 可以做到这一点,而且它本身并不是通用的。 fromJson 可能需要 TypeToken&lt;T&gt;,但还有其他方法可以获取 Type,因此会受到限制。

返回 Object 并强制调用者进行未经检查的强制转换会更加透明,但 Gson 开发人员可能希望避免这种“丑陋”。

【讨论】:

  • 进一步跟踪代码,我看到 typeOfT 被转换为 Class。代码在 TypeUtils 类中。
  • @Ritesh 你介意链接到那个吗?我没看到。
  • @Ritesh 抱歉,我只是想理解您所说的“进一步遵循代码”是什么意思——将toRawClass 链接回fromJson 的调用层次结构是什么?
  • 我刚刚浏览(或单击)了您添加的链接处的源代码并找到了参考。我对 Gson 并不熟悉,但总体而言(来自 javadocs 和 TypeToken、JsonDeserializationContext、JsonDeserializationVisitor、ParameterizedTypeHandlerMap 和 ParameterizedTypeImpl 等类)似乎有大量代码可以为任何参数化类型提供功能(此外对于 Class) 的简单情况,它不是类型推断。可以注册新的类型适配器来自定义序列化(参见 com.google.gson.JsonSerializer 的 javadoc)。
【解决方案2】:
T list = g.fromJson(jsonString,tToken);

g.fromJson().的返回类型推断

【讨论】:

  • 我已经尝试过了,但我猜不是这样...请查看我的编辑...感谢您的回复..:)
  • g.fromJson 的返回类型是从方法参数 'tToken' (Type) 推断出来的
  • @Ritesh:我知道fromJson 方法的返回类型是从类型参数推断出来的,我想知道的是该推断如何帮助推断T 的类型我的方法
  • 不确定我明白了。根据定义,泛型方法使用类型参数来表达一个或多个参数的类型和/或其返回类型之间的依赖关系。如果你看到 fromJson 的签名,你会发现它使用类型参数来表示它的参数 (typeOfT) 决定返回类型 T。你的方法 fromJsonArray 也是一个泛型方法 - 它的第二个参数是 typeOfT,它的签名表明该返回类型是同一类型。
  • 当然是这样。仅仅因为您无法获得自己的错误示例进行编译,这并不能证明什么。 fromJson() 方法确实编译。
猜你喜欢
  • 2014-12-27
  • 1970-01-01
  • 1970-01-01
  • 2018-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多