用于地图值的Java泛型

use*_*188 0 java generics

我想在以下场景中直接转换为适当的列表类型.如果擦除会杀死类类型信息,那么在以下场景中维护类类型的最佳方法是什么?

class MyProperties {

    private final Map<String, List<?>> properties = new HashMap<>();

    public void put(final String key, final List<?> value) {
        properties.put(key, value);
    }

    public <T> List<T> get(final String key, final Class<T> t) {
        final List<?> object = properties.get(key);
        // Here is where the issue lies because the class type is gone, so we
        // will always skip this block and return null
        if (object.getClass().isAssignableFrom(t)){
            return (List<T>)object;
        }
        return null;
    }

    public static void main(String[] args){
        List<String> myList = new ArrayList<>();
        myList.add("Bacon");
        myList.add("Eggs");

        List<Integer> myList2 = new ArrayList<>();
        myList2.add(1);
        myList2.add(2);

        MyProperties props = new MyProperties();
        props.put("myKey", myList);
        props.put("myKey2", myList2);

        // I'd like to directly cast to the appropriate list type if possible
        List<String> foo = props.get("myKey", String.class);
        List<Integer> foo2 = props.get("myKey2", Integer.class);
    }    
}
Run Code Online (Sandbox Code Playgroud)

Tho*_*mas 6

存储元素类型

您可以做的是传递列表元素的类并单独存储它:

private final Map<String, List<?>> properties = new HashMap<>();
private final Map<String, Class<?>> propClasses= new HashMap<>();

public <T> void put(final String key, final List<T> value, final Class<T> elementClass) {
  properties.put(key, value);
  propClasses.put(key, elementClass);
}
Run Code Online (Sandbox Code Playgroud)

正如您已经指出的,由于类型擦除,您不知道列表元素的类型.因此,您需要单独存储该类,因此需要第二个映射.

要确保传递正确的类,请更改put方法以获取类参数,该类参数必须与列表的泛型类型相同.但请注意,您仍然可以使用原始类型禁用类型检查.

例:

//works because the type is Integer for the list and the class
put( "working", new ArrayList<Integer>(), Integer.class ); 

//doesn't work since Number != Integer, even though Integer extends Number
put( "error", new ArrayList<Number>(), Integer.class );
put( "still an error", new ArrayList<Integer>(), Number.class );

//careful, this compiles because the raw type List is used
//the compiler would warn us though, so don't take those warnings lightly :)
put( "raw", (List)new ArrayList<Number>(), Integer.class );
Run Code Online (Sandbox Code Playgroud)

检索具有确切元素类型的列表

public <T> List<T> get(final String key, final Class<T> t) {
  Class<?> propClass = propClasses.get( key );                  
  if( propClass == null || t == null || !t.equals( propClass )  ) {
    return null;
  }

  return (List<T>)properties.get(key);        
}
Run Code Online (Sandbox Code Playgroud)

获取元素时,首先在相应的映射中获取类,并检查t类本身还是超类.如果成功,您将从属性映射中获取列表并将其转换为您需要的类型.

t.equals( propClass )因为你不应该将列表强制转换为超类型的元素.否则,您将能够插入错误类型的元素.

作为一个例子假设List<Integer>.以下会导致问题:

//assume this were allowed by your code 
List<Number> l = get("working", Number.class ); 
l.add( new Double( 0.5 ) ); //ouch, now we have a double in an integer list 
Run Code Online (Sandbox Code Playgroud)

检索元素类型为下限的列表(只读)

如果要使用元素类的超类,可以更改get()方法或添加"只读"方法,如下所示:

public <T> List<? extends T> getReadOnly(final String key, final Class<T> t) {
  Class<?> propClass = propClasses.get( key );          
  if( propClass == null || t == null || !t.isAssignableFrom( propClass ) ) {
    return null;
  }

  return (List<? extends T>)properties.get(key);        
}
Run Code Online (Sandbox Code Playgroud)

如果T是元素类或超类,我们允许转换为List<? extends T>.因此编译器允许我们使用超类型访问元素,但不允许我们添加新元素 - 除非我们通过手动强制转换来破坏它.

例:

List<? extends Number> l = getReadOnly( "working", Number.class );

//won't compile since the compiler isn't sure about the type of the list
//in our case it would actually be a List<Integer> but the compiler doesn't know that
//and thus disallows adding to the list - which is good since we'd produce a bug here
l.add( new Double(0.5));

//this would compile due to the raw type, which essentially disables type checks here
//fortunately the compiler warns us not to do this or at least be very careful
((List)l).add( new Double(0.5));
Run Code Online (Sandbox Code Playgroud)