利用Jdk 6260652 Bug解析Arrays.asList
在java.util.arraylist源码中:
c.toarray might (incorrectly) not return object[] (see 6260652) 产生疑惑:
附上java bug 网址: java bug database
,可以根据关键词或bug id 查询详细信息
这个bug的描述中可以看出:
原因:arrays内部实现的arraylist的toarray()方法的行为与规范不一致。
代码测试:
import java.util.*; public class test{ public static void demo1(){ system.out.println("this is demo1"); list<string> list=new arraylist<>(); list.add("张三"); list.add("王五"); object[] arr=list.toarray(); system.out.println(arr.getclass().getcanonicalname()); arr[0]=new object(); test.printarr(arr); /* 正常编译、执行: this is demo1 java.lang.object[] java.lang.object@15db9742 王五 */ } public static void demo2(){ system.out.println("this is demo2"); list<string> list = arrays.aslist("张三", "王五"); object[] arr=list.toarray(); system.out.println(arr.getclass().getcanonicalname()); arr[0]=new object(); test.printarr(arr); /* 正常编译 执行输出: this is demo2 java.lang.string[] exception in thread "main" java.lang.arraystoreexception: java.lang.object at test.demo2(test.java:31) at test.main(test.java:55) */ } public static void demo3() { system.out.println("this is demo3"); object[] arr = new string[]{"张三", "王五"}; system.out.println(arr.getclass().getcanonicalname()); arr[0] = 7; test.printarr(arr); /* 正常编译 执行输出: this is demo3 java.lang.string[] exception in thread "main" java.lang.arraystoreexception: java.lang.integer at test.demo3(test.java:48) at test.main(test.java:71) */ } public static void printarr(object[] arr) { for (object o : arr) { system.out.print(o + " "); } system.out.println(); } public static void main(string[]args){ //test.demo1(); //test.demo2(); test.demo3(); } }
输出截图:
分析过程详解:
第一步:
看arraylist带collection对象的构造函数源码(java.util.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); }
看java.util.arraylist,中toarray()源码:
public object[] toarray() { return arrays.copyof(elementdata, size); } /** * 返回 arraylist 元素组成的数组 * @param a 需要存储 list 中元素的数组 * 若 a.length >= list.size,则将 list 中的元素按顺序存入 a 中,然后 a[list.size] = null, a[list.size + 1] 及其后的元素依旧是 a 的元素 * 否则,将返回包含list 所有元素且数组长度等于 list 中元素个数的数组 * 注意:若 a 中本来存储有元素,则 a 会被 list 的元素覆盖,且 a[list.size] = null * @return * @throws arraystoreexception 当 a.getclass() != list 中存储元素的类型时 * @throws nullpointerexception 当 a 为 null 时 */ @suppresswarnings("unchecked") public <t> t[] toarray(t[] a) { // 若数组a的大小 < arraylist的元素个数,则新建一个t[]数组, // 数组大小是"arraylist的元素个数",并将“arraylist”全部拷贝到新数组中 if (a.length < size) // make a new array of a's runtime type, but my contents: return (t[]) arrays.copyof(elementdata, size, a.getclass()); // 若数组a的大小 >= arraylist的元素个数,则将arraylist的全部元素都拷贝到数组a中。 system.arraycopy(elementdata, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
可以看出,由于arraylist中elementdata类型为object[],所以调用copyof()返回值类型为object[]。
第二步:
看 arrays.aslist()源码:
public static <t> list<t> aslist(t... a) { return new arraylist<>(a); }
仔细阅读官方文档,你会发现对 aslist 方法的描述中有这样一句话:
返回一个由指定数组生成的固定大小的 list。
注意:参数类型是 t ,根据官方文档的描述,t 是数组元素的 class。
任何类型的对象都有一个 class 属性,这个属性代表了这个类型本身。原生数据类型,比如 int,short,long等,是没有这个属性的,具有 class 属性的是它们所对应的包装类 integer,short,long。
aslist 方法的参数必须是对象或者对象数组,而原生数据类型不是对象。当传入一个原生数据类型数组时,aslist 的真正得到的参数就不是数组中的元素,而是数组对象本身。(解决方案:使用包装类数组。)
继续分析:
此时的arraylist并非我们常用的java.util.arraylist,而是arrays的内部类。它继承自abstractlist,自然实现了collection接口,代码如下:
private static class arraylist<e> extends abstractlist<e> implements randomaccess, java.io.serializable { private static final long serialversionuid = -2764017481108945198l; private final e[] a; arraylist(e[] array) { if (array==null) throw new nullpointerexception(); a = array; } public int size() { return a.length; } 。。。。。。 }
ublic abstract class abstractlist<e> extends abstractcollection<e> implements list<e> {
。。。。。。。 public void add(int index, e element) { throw new unsupportedoperationexception(); } public e remove(int index) { throw new unsupportedoperationexception(); } 。。。。。。
abstractlist这个抽象类所定义的add和remove方法,仅仅是抛出了一个异常!
如果是想将一个数组转化成一个列表并做增加删除操作的话,建议代码如下:
public class test { public static void main(string[] args) { string[] myarray = { "张三", "李四", "赵六" }; list<string> mylist = new arraylist<string>(arrays.aslist(myarray)); mylist.add("王五"); } };
demo2(测试代码中的):
public static void demo2(){ system.out.println("this is demo2"); list<string> list = arrays.aslist("张三", "王五"); object[] arr=list.toarray(); system.out.println(arr.getclass().getcanonicalname()); arr[0]=new object(); test.printarr(arr); /* 正常编译 执行输出: this is demo2 java.lang.string[] exception in thread "main" java.lang.arraystoreexception: java.lang.object at test.demo2(test.java:31) at test.main(test.java:55) */ }
上面的抛出异常分析:
aslist方法直接将string[]数组作为参数传递给arraylist的构造方法,然后将string[]直接赋值给内部的a,所以a的真实类型是string[],根据jls规范string[]的clone方法返回的也是string[]类型。最终,toarray()方法返回的真实类型是string[],此时,操作arr[0]=new object();是向数组中添加object对象,就会报异常的问题了。
jdk 6260652 bug 问题是在2005年提出的,现在已经解决了,使用toarray(t[] a)避免exception的发生,所以可能会导致类型不匹配的错误。
小总结:
arrays.aslist()的使用方法:
该方法是将数组转化为list。有以下几点需要注意:
1.该方法不适用于基本数据类型(byte,short,int,long,float,double,boolean)
解决方案:使用包装类数组,例子如下:
public class test { public static void main(string[] args) { integer[] myarray = { 1, 2, 3 }; list mylist = arrays.aslist(myarray); system.out.println(mylist.size()); } }
2.该方法将数组与列表链接起来,当更新其中之一时,另一个自动更新
3.不支持add和remove方法
将数组转化为一个list对象,一般会想到arrays.aslist()方法,这个方法会返回一个arraylist类型的对象。但是用这个对象对列表进行添加删除更新操作,就会报unsupportedoperationexception异常。
原因:这个arraylist类并非java.util.arraylist类,而是arrays类的静态内部类!
说明:aslist的返回对象是一个arrays内部类,并没有实现集合的修改方法。arrays.aslist体现的是适配器模式,只是转换接口,后台的数据仍是数组。
string[] str = new string[]{"张三","王五"}; list list = arrays.aslist(str);
第一种情况:list.add("赵四"); //运行时异常
第二种情况:str[0] = "大二哈"; //list.get(0)也随着修改。
此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。 除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出 nullpointerexception。
下一篇: SQL Server——死锁查看