下面的方法完美无瑕
public <T> void fromJsonArray(String jsonString,Type tToken) { Gson g = new Gson(); T list = g.fromJson(jsonString,tToken); System.out.println(list); }
但是我没有指定此方法中的是什么。编译器如何将fromJson方法返回的值分配给list未指定类型i 的变量?
fromJson
list
我只是测试了答案的有效性,指出了<T>从该方法的返回类型推断出的答案。它似乎没有解决。请检查以下代码。它甚至不编译
<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()
该方法如何推断类型<T>
没有。泛型方法不推断其泛型类型-这就是为什么T称为 类型参数 的原因。方法的 调用者 提供的类型参数T。如果这样做,编译器 可能 会根据方法调用的参数和目标类型的上下文来推断出它。
T
例如:
Set<String> c = Collections.emptySet();
emptySet声明类型参数T,不带参数,并返回Set<T>。在此,编译器推断T是String基于目标类型Set<String>。
emptySet
Set<T>
String
Set<String>
另一个例子:
Collections.singleton("asdf");
singleton声明一个类型参数T,使用T,然后返回Set<T>。在这里,没有目标类型,但是编译器推断T是String基于参数"asdf"。
singleton
"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除非使用类型见证,否则其类型参数将默认为其上限。
fromJsonArray
Object
myObj.<String>fromJsonArray(jsonString, tToken);
但这没关系,因为<String>对方法调用或其编译的行为没有影响。T是没有意义的*,可以从的声明中删除fromJsonArray。
<String>
编译器如何将fromJson方法返回的值分配给list未指定类型i 的变量?
这是来源Gson.fromJson(String, Type):
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已被擦除。您可以看到代码抑制了这样做的警告,因为通常这是一个坏主意。通过不限制T基于方法参数的内容,Gson代码已有效地将其控制权转让给了调用者。如果您写:
List<String> list = g.fromJson(jsonString, tToken);
但用tToken表示HashSet<String>,您将ClassCastException在运行时在该行上得到一个。更糟糕的是,如果tToken表示的话ArrayList<Integer>,它甚至不会在那条线上失败,因为JVM只会看到List并允许分配发生。ClassCastException一旦您的代码尝试将列表的Integer元素处理为Strings ,便会在以后的某个时间抛出A (并且此异常会导致调试混乱)。
tToken
HashSet<String>
ClassCastException
ArrayList<Integer>
List
Integer
因此,要回答有关分配的问题,编译器可让您将结果分配给所需的fromJson任何对象。正确无误取决于您。
您可能会问, 为什么Gson会进行未经检查的强制转换并允许使用不安全的代码? 答案是由于语言限制,这很方便。他们的其他签名更安全:
public <T> T fromJson(String json, Class<T> classOfT)
但是没有办法用Class- 来表示通用类型List<String>.class。只有一个Type可以做到这一点,它本身并不是通用的。fromJson可能需要一个TypeToken<T>,但是还有其他方法可以获取Type,因此这是限制性的。
Class
List<String>.class
Type
TypeToken<T>
返回Object并强制调用者进行未经检查的强制转换会更加透明,但是Gson开发人员可能希望避免这种“丑陋”。