本文主要是介绍ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[],希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
错误堆栈:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]at android.support.v4.util.SimpleArrayMap.allocArrays(SourceFile:183)at android.support.v4.util.SimpleArrayMap.put(SourceFile:437)
错误原因:
由于SimpleArrayMap 里面使用了一个静态变量的缓存,mBaseCache,
static Object[] mBaseCache;
该变量默认有两个数据,第1个元素是一个object[],用于存放上次的缓存的mBaseCache
第二个元素是int[],用于存在hash。具体赋值代码可以看下面
synchronized (ArrayMap.class) {if (mBaseCacheSize < CACHE_SIZE) {array[0] = mBaseCache;array[1] = hashes;for (int i=(size<<1)-1; i>=2; i--) {array[i] = null;}mBaseCache = array;mBaseCacheSize++;if (DEBUG) Log.d(TAG, "Storing 1x cache " + array+ " now have " + mBaseCacheSize + " entries");}}
使用该数组的地方在:
SimpleArrayMap 的allocArrays 方法里
synchronized (ArrayMap.class) {if (mBaseCache != null) {final Object[] array = mBaseCache;mArray = array;mBaseCache = (Object[])array[0];mHashes = (int[])array[1];array[0] = array[1] = null;mBaseCacheSize--;if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes+ " now have " + mBaseCacheSize + " entries");return;}}
下面这段代码是有风险的,如果mBaseCache 在多线程被修改了,就会抛ClassCastException 异常。
mBaseCache = (Object[])array[0];
解决方法:
如果项目某个地方报这个错误,请把这个地方的ArrayMap替换成 ConcurrentHashMap. 不需要把项目中所有的地方都替换掉,没有必要。单独线程,ArrayMap 完全没有问题。
错误复现
这个复现起来超级麻烦,我花了一周的时间,才找到复现的漏洞,分享给大家:
/*** 复现该问题 用了四个线程* java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]* at android.support.v4.util.SimpleArrayMap.allocArrays(SimpleArrayMap.java:157)* at android.support.v4.util.SimpleArrayMap.put(SimpleArrayMap.java:399)* at com.example.fragment.MainFragment$14.run(MainFragment.java:280)* 1.首先 线程1 执行到put 方法的* mArray[index<<1] = key;* mArray[(index<<1)+1] = value;* mSize++;* return null;* 最上面这个位置 目的是让这个数组不再是空的** 2.执行线程2 也执行到* mArray[index<<1] = key;* mArray[(index<<1)+1] = value;* mSize++;* return null;* 最上面这个位置 目的是让这个put 的东西,放在第0个位置,因为put里面会生成index,* 让两个线程都放到index 是0 的位置** 3.把线程1执行完,这样数据里面已经放进去一个数据了** 4.执行线程3 到removeAt 方法的 freeArrays 的 mBaseCache = array; 之前* public V removeAt(int index) {* final Object old = mArray[(index << 1) + 1];* if (mSize <= 1) {* // Now empty.* if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");* freeArrays(mHashes, mArray, mSize);** mBaseCache = array;----------- freeArrays** 这个的目的是调用freeArray 方法,让当前的map释放当前的数组。这样就可以生成mBaseCache了** 5.把线程2 执行完* 这样就会把mBaseCache 赋值的数组,重新赋值** 6.把线程3执行完* ok,现在mBaseCache已经被污染了** 7.执行线程4**/private void CMETestCastException() {final ArrayMap testArrayMap = new ArrayMap();final ArrayMap testArrayMap2 = new ArrayMap();new Thread("线程1"){@Overridepublic void run() {super.run();testArrayMap.put("2324","fffff");}}.start();new Thread("线程2"){@Overridepublic void run() {super.run();testArrayMap.put("test","string");}}.start();new Thread("线程3"){@Overridepublic void run() {super.run();testArrayMap.removeAt(0);}}.start();new Thread("线程4"){@Overridepublic void run() {super.run();testArrayMap2.put("aaa","string");}}.start();}
复现这个问题的时候,关键是把mBaseCache 污染掉。这里四个线程的话,需要调试,调试步骤就是上面我注释的。
总结:
如果当前的map 会有多个线程访问,请使用HasMap. 该问题,google 并没有解决。在高版本上,直接扔CME (ConcurrentModificationException.)
这篇关于ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!