Android代码 - SharedPreferences类导出用于持久化/检索不同首选项的不同方法:
@SuppressWarnings("unchecked")
public static <T> T retrieve(Context ctx, String key, T defaultValue) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
if (defaultValue instanceof Boolean) return (T) (Boolean) prefs
.getBoolean(key, (Boolean) defaultValue);
else if (defaultValue instanceof Float) return (T) (Float) prefs
.getFloat(key, (Float) defaultValue);
// etc - another 4 cases
}
这有效,我可以致电boolean stored = retrieve(ctx, "BOOLEAN_KEY", true)
好吧 - 但我的问题是:因为我已经使用过instanceof
而T
已经归结为某个特定类,有没有办法避免单人和双人演员以及warning : unchecked
?
编辑 :如果我要通过课程,请拨打getBoolean()
或getFloat()
等。我想要的是简化方法的内部并消除警告,但仍然可以调用retrieve(ctx, "KEY", 1 or "string" or true)
并得到我想要的东西
答案 0 :(得分:4)
简短回答:不,你无法摆脱警告。他们是有原因的。
更长的答案:正如您所知,Java中的泛型只是语法糖加上编译时检查;几乎所有东西都没有存活到运行时(称为“擦除”的过程)。这意味着您方法中的(T)
转换实际上是无操作。它将转变为最具体的类型,在这种情况下是Object
。所以这个:
(T) (Boolean) prefs.whatever()
真的变成了这个:
(Object) (Boolean) prefs.whatever()
当然与以下相同:
(Boolean) prefs.whatever()
这会让你陷入危险的境地,这就是警告试图告诉你的。基本上,你正在失去类型安全性,它最终可能会让你远离那个bug实际上的位置(因而很难追踪)。想象一下:
// 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”在prefs中,你会调用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
而不是prefs对象,只是为了简单起见。您可以复制粘贴此示例并按原样进行试用。
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);
}
}
有几点需要注意:
main
)。错误发生在prefsToMap
,但main
支付了费用。如果map
是实例变量,则非常很难跟踪引入错误的位置。getNumber
与您的retrieve
功能defaultVal
是Long
,我会得到(并转换为T
) double 而不是long。但类型系统无法捕获这个错误,这正是未经检查的演员试图警告我的事情(它警告我它不能捕获任何错误,而不是必然存在错误)。defaultValue
是int或String,一切都会好的。但是,如果它是长的,并且num
为空,那么当呼叫网站需要Double
时,我将返回Long
。prefsToMap
类仅转换为T
- 如上所述,这是一个无操作转换 - 它不会导致任何强制转换异常。直到倒数第二行Long fromMap = map.get("foo")
。使用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
的结果转换为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
类型 - 也就是对象 - 它根本不进行任何转换。
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
答案 1 :(得分:2)
正常的方法是使用Class.cast(obj),biut你需要一个T类的实例,这通常是通过将一个传入方法来完成的,但在你的情况下会很好:
return Boolean.class.cast(pregs.getBoolean(key, (Boolean)defaultValue));
编辑:评论之后,是的,这可能是类型不匹配的问题。
您需要将类类型作为方法的一部分传入,或者从默认值推断它(如果它不是null:
return defaultValue.getClass().cast(pregs.getBoolean(key, (Boolean)defaultValue));
使用工作示例再次编辑(这对我没有任何警告):
public class JunkA {
private boolean getBoolean(String key, boolean def) {
return def;
}
private float getFloat(String key, float def) {
return def;
}
private String getString(String key, String def) {
return def;
}
private int getInt(String key, int def) {
return def;
}
public <T> T getProperty(final Class<T> clazz, final String key,
final T defval) {
if (clazz.isAssignableFrom(Boolean.class)) {
return clazz.cast(getBoolean(key, (Boolean) defval));
}
if (clazz.isAssignableFrom(String.class)) {
return clazz.cast(getString(key, (String) defval));
}
if (clazz.isAssignableFrom(Boolean.class)) {
return clazz.cast(getFloat(key, (Float) defval));
}
if (clazz.isAssignableFrom(Integer.class)) {
return clazz.cast(getInt(key, (Integer) defval));
}
return defval;
}
}
答案 2 :(得分:0)