views:

390

answers:

3

I'm relatively new to Java and am used to generics in C# so have struggled a bit with this code. Basically I want a generic method for getting a stored Android preference by key and this code, albeit ugly, works for a Boolean but not an Integer, when it blows up with a ClassCastException. Can anyone tell me why this is wrong and maybe help me improve the whole routine (using wildcards?)?

    public static <T> T getPreference(Class<T> argType, String prefKey, T defaultValue,
        SharedPreferences sharedPreferences) {

    ...

    try {
        if (argType == Boolean.class) {
            Boolean def = (Boolean) defaultValue;
            return argType.cast(sharedPreferences.getBoolean(prefKey, def));
        } else if (argType == Integer.class) {
            Integer def = (Integer) defaultValue;
            return argType.cast(new Integer(sharedPreferences.getInt(prefKey, def)));
        } else {
            AppGlobal.logWarning("getPreference: Unknown type '%s' for preference '%s'. Returning default value.",
                    argType.getName(), prefKey);
            return defaultValue;
        }
    } catch (ClassCastException e) {
        AppGlobal.logError("Cast exception when reading pref %s. Using default value.", prefKey);
        return defaultValue;
    }
}

My calling code is:

        mAccuracy = GlobalPreferences.getPreference(Integer.class, prefKey, mAccuracy, sharedPreferences);

Here is the Android code for getInt():

public int getInt(String key, int defValue) {
        synchronized (this) {
            Integer v = (Integer)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

I've tried various ways - using the native int, casting to an Integer, but nothing works.

A: 

May I suggest:

Integer i = new Integer(42);
Xavier Ho
Unless I'm mistaken by your question and what you need to accomplish, this should work given the input is an `int`.
Xavier Ho
Sorry, that doesn't work. That was my original code. In fact, I am going to edit the sample to show that. Thanks
Rob Kent
I corrected my sample thanks and showed the Android source being called.
Rob Kent
I assumed you've also tried: `return new Integer(sharedPreferences.getInt(prefKey, def));` ?
Xavier Ho
A: 

Try defining a bunch of functions with the same name that take a different type for the default, and let the compiler figure it out. Java really ties your hands when working with types, especially primitive types.

public function int getPreference( String key , int missing ) { return sharedPreferences.getInt( key , missing ); }
public function boolean getPreference( String key , boolean missing ) { return sharedPreferences.getBoolean( key , missing ); }
public function String getPreference( String key , String missing ) { return sharedPreferences.getString( key , missing ); }

Edit:

If you are trying to get an object (not primitive) regardless of the type, you can use:

public function Object getPreference( String key , Object missing ) { return sharedpreferences.contains( key ) ? sharedPreferences.getAll().get( key ) : missing; }

If you are trying to get a specific type like int regardless of what is stored, you can use:

public function int getPreference( String key , int missing ) {
    int result = missing;
    Object value = sharedpreferences.contains( key ) ? sharedPreferences.getAll().get( key ) : null;
    if ( value instanceof Integer ) result = ((Integer)value).intValue();
    if ( value instanceof Boolean ) result = ((Boolean)value).booleanValue() ? 1 : 0;
    // repeat for every other primitive wrapper type you care about
    return result;
}

If you are trying to get a result only if it is a certain type, you can use something like:

public function Object getPreference( Class inRequire , String key , Object missing ) {
    Object value = sharedpreferences.contains( key ) ? sharedPreferences.getAll().get( key ) : null;
    if ( !( value instanceof inRequire ) ) {
        value = null;
    }
    return ( value == null ) ? missing : value;
}
drawnonward
I thought that that was generics were all about :) I'd rather use generics if possible. But if I can't, I'll do what you suggest. Thanks
Rob Kent
Java is not flexible like other languages, and primitives are not objects so they cannot mix in return types or function arguments. You can return an Object or a specific primitive like int or boolean but it has to be decided at compile time.
drawnonward
Thanks - that is useful but it is still returning a Object, isn't it? not a strongly typed object.
Rob Kent
A: 

It turns out the preference I'm trying to read is stored as a string so the cast exception is coming from inside the Android code not mine. Thanks for your help. But as I am a Java-newbie, if you think there is anything generally wrong with my routine, please teach me a better way.

Rob Kent
The API specifically state that all of the `getType` methods from `SharedPreferences` throw a `ClassCastException` if you attempt to fetch a preference with a name that is not of the same type as the type corresponding to the `getType` method.
JRL
That's right. My next question is how do you have a ListPreference whose values are an array of ints.
Rob Kent