简短的回答:不,你无法摆脱警告。他们在那里是有原因的。
更长的答案:正如你所知,Java 中的泛型只是语法糖加上编译时检查;几乎没有任何东西能存活到运行时(这个过程称为“擦除”)。这意味着演员阵容(T)
在你的方法中实际上是一个空操作。它将转换为最具体的类型,在本例中是Object
。所以这:
(T) (Boolean) prefs.whatever()
真的变成了这样:
(Object) (Boolean) prefs.whatever()
这当然与以下内容相同:
(Boolean) prefs.whatever()
这可能会让您陷入危险的境地,这正是警告试图告诉您的。基本上,您正在失去类型安全性,并且它最终可能会让您远离错误的实际位置(因此很难追踪)。想象一下以下情况:
// wherever you see "T" here, think "Object" due to erasure
public <T> void prefsToMap(String key, T defaultValue, Map<String, T> map) {
T val = retrieve(this.context, key, defaultValue);
map.put(key, val);
}
Map<String,Integer> map = new HashMap<>();
prefsToMap("foo", 123, map);
// ... later
Integer val = map.get("foo");
到目前为止一切顺利,在你的情况下它会起作用,因为如果“foo”在首选项中,你会调用getInt
为拿到它,为实现它。但想象一下,如果你的程序中有一个错误retrieve
函数,使得if( defaultValue instanceof Integer)
不小心回来了getDouble()
代替getInt()
(包括选角和所有这些)。编译器不会捕获它,因为你的转换为T
实际上只是一个演员Object
,这始终是允许的!你不会发现直到Integer val = map.get("foo");
,变成:
Integer val = (Integer) map.get("foo"); // cast automatically inserted by the compiler
这个强制转换可能离错误真正发生的地方很远——getObject
打电话——很难追踪。 Javac 正在努力保护您免受这种情况的影响。
这是一个将所有内容放在一起的示例。在此示例中,我将使用Number
而不是首选项对象,只是为了简单起见。您可以复制粘贴此示例并按原样尝试。
import java.util.*;
public class Test {
@SuppressWarnings("unchecked")
public static <T> T getNumber(Number num, T defaultVal) {
if (num == null)
return defaultVal;
if (defaultVal instanceof Integer)
return (T) (Integer) num.intValue();
if (defaultVal instanceof String)
return (T) num.toString();
if (defaultVal instanceof Long)
return (T) (Double) num.doubleValue(); // oops!
throw new AssertionError(defaultVal.getClass());
}
public static void getInt() {
int val = getNumber(null, 1);
}
public static void getLong() {
long val = getNumber(123, 456L); // This would cause a ClassCastException
}
public static <T> void prefsToMap(Number num, String key, T defaultValue, Map<String, T> map) {
T val = getNumber(num, defaultValue);
map.put(key, val);
}
public static void main(String[] args) {
Map<String, Long> map = new HashMap<String,Long>();
Long oneTwoThree = 123L;
Long fourFixSix = 456L;
prefsToMap(oneTwoThree, "foo", fourFixSix, map);
System.out.println(map);
Long fromMap = map.get("foo"); // Boom! ClassCastException
System.out.println(fromMap);
}
}
有几点需要注意:
-
大的那个:尽管泛型应该为我提供类型安全性,但我得到了 ClassCastException。不仅如此,我在一段完全没有错误的代码中遇到了错误(
main
)。错误发生在prefsToMap
, but main
支付了费用。如果map
是一个实例变量,它可以是very很难跟踪该错误是在哪里引入的。
- 除了使用数字而不是首选项之外,我的
getNumber
和你的几乎一样retrieve
功能
- 我故意制造了一个错误:如果
defaultVal
is a Long
,我得到(并投射到T
) a double而不是长。但是类型系统无法捕获此错误,这正是未经检查的强制转换试图警告我的内容(它警告我它无法捕获任何错误,而不是一定存在错误)。
- If
defaultValue
是 int 或 String,一切都会好起来的。但如果它是一个 Long,并且num
为空,那么我将返回一个Double
例如,当调用站点期望Long
.
- 因为我的
prefsToMap
类仅强制转换为T
-- 如上所述,这是一个无操作强制转换 -- 它不会导致任何强制转换异常。直到倒数第二行我才得到异常,Long fromMap = map.get("foo")
.
Using javap -c
,我们可以看到其中一些在字节码中的样子。首先,我们看一下getNumber
。请注意,强制转换为T
不要显示为任何内容:
public static java.lang.Object getNumber(java.lang.Number, java.lang.Object);
Code:
0: aload_0
1: ifnonnull 6
4: aload_1
5: areturn
6: aload_1
7: instanceof #2; //class java/lang/Integer
10: ifeq 21
13: aload_0
14: invokevirtual #3; //Method java/lang/Number.intValue:()I
17: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
20: areturn
21: aload_1
22: instanceof #5; //class java/lang/String
25: ifeq 33
28: aload_0
29: invokevirtual #6; //Method java/lang/Object.toString:()Ljava/lang/String;
32: areturn
33: aload_1
34: instanceof #7; //class java/lang/Long
37: ifeq 48
40: aload_0
41: invokevirtual #8; //Method java/lang/Number.doubleValue:()D
44: invokestatic #9; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
47: areturn
48: new #10; //class java/lang/AssertionError
51: dup
52: aload_1
53: invokevirtual #11; //Method java/lang/Object.getClass:()Ljava/lang/Class;
56: invokespecial #12; //Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
59: athrow
接下来,看一下getLong
。请注意,它投射的结果是getNumber
to a Long
:
public static void getLong();
Code:
0: bipush 123
2: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: ldc2_w #15; //long 456l
8: invokestatic #17; //Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
11: invokestatic #13; //Method getNumber:(Ljava/lang/Number;Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #7; //class java/lang/Long
17: invokevirtual #18; //Method java/lang/Long.longValue:()J
20: lstore_0
21: return
最后,这是prefsToMap
。请注意,因为它只处理通用的T
type——又名对象——它根本不做任何转换。
public static void prefsToMap(java.lang.Number, java.lang.String, java.lang.Object, java.util.Map);
Code:
0: aload_0
1: aload_2
2: invokestatic #13; //Method getNumber:(Ljava/lang/Number;Ljava/lang/Object;)Ljava/lang/Object;
5: astore 4
7: aload_3
8: aload_1
9: aload 4
11: invokeinterface #19, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
16: pop
17: return