欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > > 文章正文

基于Java8的ArrayList常用方法详解,java8arraylist

来源: javaer 分享于  点击 28672 次 点评:199

基于Java8的ArrayList常用方法详解,java8arraylist


在Java里面ArrayList算得上是一个常用的数据结构,同时也是一个线程不安全的数据结构。

构造函数

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

一般常用的是第一种和第二种,第三种用的比较少。从代码能看出来,如果调用构造函数的时候,不传参数就使用默认的空对象内存(这有点不好形容,是分配了内存,但是没有写数据),传了参数就直接构造相应大小的数组,集合一类的直接转换成数组。构造函数没什么好讲的,就那样。

add方法

用过ArrayList的应该都用过add方法。add一共重载了两个方法。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //确实容器数量足够
        elementData[size++] = e;
        return true;
    }
    public void add(int index, E element) {
        rangeCheckForAdd(index);//检查坐标是否合法,防止数组越界
        ensureCapacityInternal(size + 1);  //
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }

add(E e)这个方法是默认从当前数据末尾插入数据。这里可以看出ArrayList和HashMap扩容策略的不同,ArrayList是先扩大了的数组再添加数据,而HashMap是先添加数据再扩大容器。之所以两者扩容策略不一样是因为HashMap在使用了数组3/4大小的时候就进行扩容,而ArrayList是要用完了整个数组才会扩容,并且ArrayList一次只增加一半的大小,这个比HashMap一次翻一倍扩大方式要相对节约一点点。附上ArrayList源码扩容策略的核心源码,HashMap请查看我之前的文章:浅谈Java8的HashMap的扩容策略。

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
    int newCapacity = oldCapacity + (oldCapacity >> 1);

add(int index,E e)和add(E e)的用法大同小异,多了一个数据移动过程。

remove方法
划重点!!!

remove也重载了两个方法。

    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        elementData[--size] = null; 
        return oldValue;
    }
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

看到这两个方法的区别了吧,一个是基于数组坐标移除,返回移除的值或者抛出异常,一个是基于对象本身的移除,返回的是true或者false。来一组很有意思的测试案例。

  public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    list.add(2);
    list.add(333);
    list.add(444);
    list.add(555);
    list.remove(2);
  }

是移除索引为2的对象444这个数据还是说移除索引为0的对象2,凭直觉告诉我答案是什么?
答案是:移除索引为2的对象444。
再来一组更有意思的测试。

    byte x1 = 2;
    list.remove(x1);
    short x2 = 2;
    list.remove(x2);
    long x3 = 2;
    list.remove(x3);

还是上面那个集合,分别执行这三组代码,结果是一样吗?答案是否定的,前两个会移出索引为2的对象444,第三个不会移除任何数据。基础的重要性在这两个问题体现的淋漓尽致。我承认这两个问题有一定的刁难成分在里面,但两个问题的答案全是干货,包含了Java5过后的自动装箱拆箱机制、基础数据类型的隐式转换。

基础数据类型隐式转换

    byte a = 2;
    short b = a;
    int c =b;
    long d = c;

Java5的自动装箱和拆箱机制

    int a = 2;
    Integer b = a;
    Integer c = 2;
    int d = c;

因为隐式转换的存在,前两个答案没有任何问题。我讲讲为什么第三种方式没有移除任何数据。首先long类型是不能自动转成int类型的,所以不能调用remove(int index)这个方法,那么就只能调用remove(Object o)这个方法,long的封装类型为Long,并且Long重写了equals方法。
源码如下

    public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
    }

这个方法一直是返回的false,所以在remove(Object o)里面,不会执行任何的移除数据操作。

clear真的clear吗?

    public void clear() {
        modCount++;
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

源码可以清楚的看见,只是把数组持有的对象充值为null。但这并不是数组为空的,来张图说明。

简单的说clear这个方法只是把过程二的引用给断开了,但过程一的引用还是存在的,当你ArrayList的数组扩充的很大的时候,clear方法并不能起到调用者想要的内存释放作用的。如果深究,这个只是断开了引用,要等到GC触发的时候才会真的回收内存的,当然这个就有点扯远了。

isEmpty、size、get、set

    public boolean isEmpty() {
        return size == 0;
    }
    public int size() {
        return size;
    }
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

因为ArrayList是一个线程不安全的数据结构,所以这个两个方法上面并没有任何亮点,只有索引是否越界的检查。
ArrayList常用方法的讲解就到这里。

如有疑问,欢迎留言!

相关文章

    暂无相关文章

用户点评