为什么 HashMap 值不在 List 中强制转换?

我在散列表中输入值,

Map<Long, Double> highLowValueMap=new HashMap<Long, Double>();
highLowValueMap.put(1l, 10.0);
highLowValueMap.put(2l, 20.0);

我想创建一个列表使用 values()的映射方法。

List<Double> valuesToMatch=new ArrayList<>();
valuesToMatch=(List<Double>) highLowValueMap.values();

或者

List<Double> valuesToMatch=(List<Double>) highLowValueMap.values();

然而,它抛出了一个例外:

线程“ main”java.lang 中的异常。 ClassCastException:
HashMap $Value 不能强制转换为 java.util.List

但它允许我将其传递给创建一个列表:

List<Double> valuesToMatch  = new ArrayList<Double>( highLowValueMap.values());
92932 次浏览

TL;DR

List<V> al = new ArrayList<V>(hashMapVar.values());

Explanation

Because HashMap#values() returns a java.util.Collection<V> and you can't cast a Collection into an ArrayList, thus you get ClassCastException.

I'd suggest using ArrayList(Collection<? extends V>) constructor. This constructor accepts an object which implements Collection<? extends V> as an argument. You won't get ClassCastException when you pass the result of HashMap.values() like this:

List<V> al = new ArrayList<V>(hashMapVar.values());

Going further into the Java API source code

HashMap#values(): Check the return type in the source, and ask yourself, can a java.util.Collection be casted into java.util.ArrayList? No

public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}

ArrayList(Collection): Check the argument type in the source. Can a method which argument is a super type accepts sub type? Yes

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}

Well it's because your values are really a HashSet. You could write a code like this to iterate over the set:

List<Double> valuesToMatch=new ArrayList<>();
for(Double d : highLowValueMap.values(){
valuesToMatch.put(d);
}

If you have already created an instance of your List subtype (e.g., ArrayList, LinkedList), you could use the addAll method.

e.g.,

valuesToMatch.addAll(myCollection)

Many list subtypes can also take the source collection in their constructor.

The answer can be found by reading the JavaDoc

The values() method returns a Collection

So

List<Double> valuesToMatch=(List<Double>) highLowValueMap.values();

Should be

Collection<Double> valuesToMatch= highLowValueMap.values();

You can still iterate over this collection as you would a list.

http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html#values%28%29


This works:

List<Double> valuesToMatch  = new ArrayList<Double>( highLowValueMap.values() );

Because ArrayList has a constructor that accepts a collection.

It's because values() returns Collection which according to source code of HashMap is of type AbstractCollection and thus cannot be cast to List.

You are able to instantiate ArrayList passing it values() result because ArrayList constructor can take Collection as its argument.

Have you check the API, what is returned by values() method? And what ArrayList constructor accepts?

Values is an inner class in HashMap class (see $ symbol in java.util.HashMap$Values).
HashMap.values() method will return Values class's object which is not implementing List interface. So is the ClassCastException.
Here is the Values inner private class in HashMap which is not implementing List interface. Even AbstractCollection is also not implementing List interface.
AbstractCollection implements Collection interface. So not able to cast to List.

private final class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return newValueIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}

Update

Following is one of the constructor in ArrayList.

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}

So hashmapObj.values() method return type is Collection. Now which class is implementing this Collection interface ? Answer is Values class which is inside the HashMap class (inner class). Returned value from hashmapObj.values() can be passed to above ArrayList constructor which is valid.

Even following is valid statements.

HashMap<String, String> map  = new HashMap<String, String>();
Collection c = map.values();

But following statements are incorrect

HashMap<String, String> map  = new HashMap<String, String>();
List c = map.values(); //compilation error.. return type is not List

I faced the same issue, But then I realised the values() return Collection, and not a List. But we are able to instantiate a new ArrayList like this :

List valuesToMatch = new ArrayList(highLowValueMap.values());

Because ArrayList has a constructor that can take Collection as its argument.

I doubt the selected best answer, where it says: "Because HashMap#values() returns a java.util.Collection and you can't cast a Collection into an ArrayList, thus you get ClassCastException."

It's not because Collection can't be casted to ArrayList, the real reason is that the Collection returned by HashMap.values() is backed up by the inner class HashMap.Values. And HashMap.Values is not a super class of ArrayList.

To convert the values from a Map instance to a list you could use Iterable<T>.map

val yourList: List<Any> = #Map.values.map { it }