Android code - the SharedPreferences class exports different methods for persisting/retrieving different preferences :
@SuppressWarnings(\"unchecked\")
public st
Short answer: no, you can't get rid of the warnings. They're there for a reason.
Longer answer: As you may know, generics in Java are just syntactic sugar plus compile-time checks; pretty much nothing survives to runtime (a process known as "erasure"). This means that the cast to (T)
within your method is actually a no-op. It'll turn into a cast to the most specific type that it can be, which in this case is Object
. So this:
(T) (Boolean) prefs.whatever()
really turns into this:
(Object) (Boolean) prefs.whatever()
which is of course the same as just:
(Boolean) prefs.whatever()
This can get you into a dangerous situation, which is what the warnings are trying to tell you. Basically, you're losing type safety, and it can end up biting you far away from where the bug actually is (and thus be hard to track down). Imagine the following:
// 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");
So far so good, and in your case it'll work, because if "foo" is in the prefs, you'll call getInt
to get it. But imagine if you had a bug in your retrieve
function, such that if( defaultValue instanceof Integer)
accidentally returned getDouble()
instead of getInt()
(with the casting and all that). The compiler won't catch it, since your cast to T
is really just a cast to Object
, which is always allowed! You won't find out until Integer val = map.get("foo");
, which becomes:
Integer val = (Integer) map.get("foo"); // cast automatically inserted by the compiler
This cast could be very far away from where the error really happened -- the getObject
call -- making it hard to track down. Javac is trying to protect you from that.
Here's an example of it all put together. In this example, I'll be using a Number
instead of a prefs object, just to keep things simple. You can copy-paste this example and try it out as is.
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);
}
}
A few things to note:
main
). The error happened in prefsToMap
, but main
paid the cost. If the map
were an instance variable, it could be very hard to track where that error was introduced.getNumber
is pretty much the same as your retrieve
functiondefaultVal
is a Long
, I get (and cast to T
) a double instead of a long. But the type system has no way of catching this bug, which is exactly what the unchecked cast is trying to warn me about (it's warning me that it can't catch any bugs, not that there necessarily are bugs).defaultValue
is an int or String, everything will be fine. But if it's a Long, and the num
is null, then I'll be returning a Double
such when the call site expects a Long
.prefsToMap
class only casts to T
-- which, as mentioned above, is a no-op cast -- it won't cause any cast exceptions. I don't get an exception until the second-to-last line, Long fromMap = map.get("foo")
.Using javap -c
, we can see how some of these look in bytecode. First, let's look at getNumber
. Note that the casts to T
don't show up as anything:
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
Next, take a look at getLong
. Notice that it casts the result of 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
And finally, here's prefsToMap
. Notice that since it only deals with the generic T
type -- aka Object -- it doesn't do any casting at all.
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
The normal way is to use the Class.cast(obj), biut you will need an instance of the T Class, this is typically done by passing one in to the method, but in your case will be fine:
return Boolean.class.cast(pregs.getBoolean(key, (Boolean)defaultValue));
EDIT: After the comment, yes, it could be a problem with the type mismatch.
You will need to pass in the class type as part of your method, or infer it from the default value (if it is not null:
return defaultValue.getClass().cast(pregs.getBoolean(key, (Boolean)defaultValue));
Editing again with working example (this works wihtout any warnings for me):
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;
}
}
Well my question is was (simply) : since I already used instanceof and T has boiled down to a specific class is there a way to avoid the single and double casts and the warning : unchecked ?
And the answer is no - I am quoting this particular answer cause it shows I am not the only one who wondered. But you may wish to vote the interesting albeit a bit off topic answer by @yshavit :)