当前位置: 移动技术网 > IT编程>开发语言>Java > 利用Jdk 6260652 Bug解析Arrays.asList

利用Jdk 6260652 Bug解析Arrays.asList

2019年05月28日  | 移动技术网IT编程  | 我要评论

在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;
        }
        。。。。。。
 }
可以发现,这里的a不是 object[],而是e[]
a称为该arraylist的backed array。同时构造函数也是直接用array给a赋值。这就是问题的所在。
另外,这个内部类里面并没有add,remove方法,它继承的abstractlist类里面有这些方法:
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。

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网