简体   繁体   中英

Unchecked cast from Object to List

I am trying to extend a solution to the problem posted here .

The castList method shared there works greats for non-generic types.

public static <T> List<T> castList(Object obj, Class<T> clazz)
{
    List<T> result = new ArrayList<T>();
    if(obj instanceof List<?>)
    {
        for (Object o : (List<?>) obj)
        {
            result.add(clazz.cast(o));
        }
        return result;
    }
    return null;
}

The issue we are having is trying to use this same idea when the return type is not something simple like a list of strings, but when the list is itself of another generic, like a list of maps.

List<Map<String, Object>> list = (List<Map<String, Object>>) out.get("o_cursor");

Trying to use castList as follows produces type mismatch: cannot convert from List<Map> to List<Map<String, Object>>. Is there a way to extend castList to do this?

List<Map<String, Object>> list = castList(out.get("o_cursor"), Map.class);

I attempted a modified version specific to lists of maps, but still get an error "The method castMapList(Object, Class<Map<K,V>>) is not applicable for the arguments (Object, Class<Map>)" which seems like a different flavor of the same error.

public static <K, V> List<Map<K, V>> castMapList(Object object, Class<Map<K, V>> clazz) {        
    if (object instanceof List<?>) {
        List <Map<K, V>> result = new ArrayList<Map<K, V>>();

        for (Object o: (List<?>) object) {
            result.add(clazz.cast(o));
        }

        return result;
    }

    return null;
}

Because of erasure, there is:

  1. No way to have a Class<Map<K, V>> .
  2. No built-in way to check at run-time that every particular Map has some particular K and V .

That is:

  1. As you found out, every generic class has a corresponding raw type Class object, eg Map.class is a Class<Map> .
  2. Something like obj instanceof Map<K, V> is a compiler error and Class objects do not check generic arguments with eg isInstance and cast .

Point-being, you can't validate generics at run-time without some extra work.

You can still program around it, depending on what you're doing. So for example, one simple way is just to rebuild each Map , the same way you rebuild the List :

static <K, V> List<Map<K, V>> castMapList(
        Object obj, Class<K> keyType, Class<V> valueType) {
    if (obj instanceof List<?>) {
        List<Map<K, V>> result = new ArrayList<>();

        for (Object element : (List<?>) obj) {
            if (element instanceof Map<?, ?>) {
                Map<K, V> map = new HashMap<>();

                for (Map.Entry<?, ?> entry : (Map<?, ?>) element) {
                    map.put(keyType.cast(entry.getKey()),
                            valueType.cast(entry.getValue());
                }

                result.add(map);
            } else {
                // do something that you want?
                // personally I would throw ClassCastExceptions
                // but you seem to be returning null
            }
        }

        return result;
    }
    return null;
}

Also, for example, it looks kind of like you seem to be doing some sort of deserialization, and you can bake the Class right in to the Map that gets serialized:

public class MyCheckedMap<K, V>
extends SomeMapImplementation<K, V>
implements Serializable {
    private static final long serialVersionUID = 1L;
    private final Class<K> keyType;
    private final Class<V> valueType;

    public MyCheckedMap(Class<K> keyType, Class<V> valueType) {
        this.keyType = Objects.requireNonNull(keyType);
        this.valueType = Objects.requireNonNull(valueType);
    }

    @SuppressWarnings("unchecked")
    public static <K, V> MyCheckedMap<K, V> cast(
            Object obj, Class<K> keyType, Class<V> valueType) {
        if (!(obj instanceof MyCheckedMap<?, ?>))
            throw new ClassCastException(obj.getClass().toString());

        MyCheckedMap<?, ?> map = (MyCheckedMap<?, ?>) obj;
        validate(keyType, map.keyType);
        validate(valueType, map.valueType);

        return (MyCheckedMap<K, V>) obj;
    }

    private static void validate(Class<?> lhs, Class<?> rhs) {
        if (lhs == rhs)
            return;
        throw new ClassCastException(String.format("%s != %s", lhs, rhs));
    }
}

You have to write some boiler-plate code for each List , Map , etc., but probably not much more than you seem to be doing.

There are also some other ways to approach this sort of problem, like Neal Gafter's Super Type Tokens and Guava's TypeToken , which is a derivative of Neal Gafter's idea. These essentially use reflection to achieve something like MyCheckedMap .

I'm not sure whether or not having unchecked warnings is an issue for you or not. For the map, you could do the following:

public <K, V> List<Map<K, V>> castMapList(Object object, Class<K> class1, Class<V> class2) {
    if (object instanceof List<?>) {
        List<Map<K, V>> result = new ArrayList<>();

        for (Map<K, V> o : (List<Map<K, V>>) object) { //Unchecked cast
            result.add(o);
        }

        return result;
    }

    return null;
}

or

for (Object o : (List<?>) object) {
    if (o instanceof Map<?, ?>) {
        result.add((Map<K, V>) o); //Unchecked cast
    }
}

I don't think having a suppress warning is the worst case for you, since even in the cast method from Class.java , they do it. You must be sure that the list contains maps though.

This is what I came up with. Thanks again!

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CastUtility {

    public static <T> List<T> castList(Object object, Class<T> clazz) {
        if (object == null) {
            return null;
        }

        if (object instanceof List<?>) {
            List<T> result = new ArrayList<T>();

            for (Object o : (List<?>) object) {
                result.add(clazz.cast(o));
            }

            return result;
        }

        throw new ClassCastException();
    }

    public static <K, V> Map<K, V> castMap(Object object, Class<K> keyType,
            Class<V> valueType) {

        if (object == null) {
            return null;
        }

        if (object instanceof Map<?, ?>) {
            Map<K, V> result = new HashMap<K, V>();

            for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
                result.put(keyType.cast(entry.getKey()),
                        valueType.cast(entry.getValue()));
            }

            return result;
        }

        throw new ClassCastException();
    }

    public static <K, V> List<Map<K, V>> castMapList(Object object,
            Class<K> keyType, Class<V> valueType) {

        if (object == null) {
            return null;
        }

        if (object instanceof List<?>) {
            List<Map<K, V>> result = new ArrayList<Map<K, V>>();

            for (Object o : (List<?>) object) {
                result.add(castMap(o, keyType, valueType));
            }

            return result;
        }

        throw new ClassCastException();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM