该方法如何推断<t> </t>的类型

时间:2014-03-09 08:29:17

标签: java generics gson

以下方法完美无缺

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

但我没有说明&lt; T>是在这种方法。编译器如何将fromJson方法返回的值赋给i型未指定的变量list

我刚刚测试了答案的有效性,说明从方法的返回类型推断<T>。它似乎没有成功。请查看以下代码。它甚至没有编译

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()

2 个答案:

答案 0 :(得分:10)

  

该方法如何推断<T>

的类型

没有。通用方法不会推断它们的泛型类型 - 这就是为什么T被称为类型参数的原因。该方法的调用者T提供了一个类型参数。如果是这样,编译器可以根据方法调用的参数和目标类型的上下文来推断

例如:

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

emptySet声明类型参数T,不带参数,并返回Set<T>。在这里,编译器根据目标类型TString推断为Set<String>

另一个例子:

Collections.singleton("asdf");

singleton声明类型参数T,取T,然后返回Set<T>。这里没有目标类型,但编译器根据参数T推断String"asdf"

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

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);

但这并不重要,因为<String>对方法调用或其编译的行为没有影响。 T无意义*,可以从fromJsonArray的声明中删除。

  

编译器如何将fromJson方法返回的值分配给i未指定的变量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<String>,您将在运行时获得该行的ClassCastException。更糟糕的是,如果tToken表示ArrayList<Integer>,它甚至不会在该行上失败,因为JVM只会看到List并允许分配发生。一旦您的代码尝试处理列表的ClassCastException元素(例如Integer s(并且异常会使调试混乱),将在稍后的某个时间抛出String

因此,为了回答有关赋值的问题,编译器允许您将fromJson的结果分配给您想要的任何内容。这取决于你是否正确。

你可能会问,为什么Gson会做一个未经检查的演员并允许不安全的代码?答案是,这是一种方便,源于语言限制。他们的另一个签名更安全:

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

但是没有办法用Class表示通用类型 - 例如没有List<String>.class。只有Type可以做到这一点,而且它本身并不通用。 fromJson可能需要TypeToken<T>,但还有其他方法可以获得Type,因此这将是限制性的。

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

答案 1 :(得分:3)

T list = g.fromJson(jsonString,tToken);

g.fromJson().

的返回类型推断出来