废话不多说 (事实是不会说)
,让我们直接进入正题
首先讲一讲最基本的ArrayList的初始化,也就是我们常说的构造函数,ArrayList给我们提供了三种构造方式,我们逐个来查看
无参的构造方法,这种方式的初始化,ArrayList内部会为我们声明一个长度为0的列表,但在我们调用add方法加入一个元素时,它内部会经历add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->grow->调用Arrays.copyOf方法返回新生成的列表(内部最终调用System.arraycopy完成元素的转移)->grow->ensureExplicitCapacity->ensureExplicitCapacity->add方法,该执行流程最终生成一个容量为十(默认值)的列表,并将元素加入到列表中。以下列出部分关键源码
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { //如果为无参的构造 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //该处的DEFAULT_CAPCITY=10,minCapcity=1 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
private void ensureExplicitCapacity(int minCapacity) { //列表结构发生改变的记录值,没发生一次结构改变(扩容、元素增加、元素删除),该值加一 modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
private void grow(int minCapacity) { ... elementData = Arrays.copyOf(elementData, newCapacity); }
也许你会问,既然有初始默认容量值,为什么不在调用构造函数的时候就初始化一个默认长度的列表呢?
个人意见:我认为在存在一种情况,就是仅声明一个ArrayList的列表对象,但是后面并未使用,在这种情况下,如果在声明是就初始化一个长度为10的列表,会造成空间的浪费,而且在真正使用列表时(即调用add方法),才初始化列表,有一种懒加载的思想在其中,避免了耗资源操作的集中,但也并非所有的构造方法中都不会初始化列表容量,在ArrayList(int capacity)构造方法中,只要输入的初始容量为正整数,那么就会在构造函数中就定义出指定大小的列表对象
该方式的初始化在执行构造函数的时候传入了一个初始容量值,不过要求这个初始容量值必须为正整数,而且如果为0的话等同于无参的构造方法,假定初始化容量为5,那么在调用add方法时,经历的方法依次为add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->add,下面我们来看看部分关键源码:
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //按指定容量初始化列表,此时elementData.length==initialCapacity this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //传入值等于0,初始化一个容量为0的初始列表 this.elementData = EMPTY_ELEMENTDATA; } else { //传入值为不符合要求,报错 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
private static final Object[] EMPTY_ELEMENTDATA = {};
private static int calculateCapacity(Object[] elementData, int minCapacity) { //不满足条件,不会进入该分支 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } //直接返回最小容量,即列表当前元素值+1(给新增元素的空间) return minCapacity; }
该方式的初始化需要传入Collection的子类对象,并根据该对象来初始化ArrayList
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { //如果不是Object[]类型的列表,转化为Object[]类型 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
到这里对ArrayList的三个构造函数做了简单的介绍
在继续进行ArrayList中方法的介绍之前,需要注意ArrayList不是线程安全的,所以如果涉及并发操作,建议在初始化的时候就调用Collections.synchronizedList(list)来将线程不安全的列表对象转化为线程安全的对象,内部的实现原理:使用代理模式在原来的方法执行之前嵌套了一个同步机制,这里摘取其中的size()方法,源码如下:
public int size() { synchronized (mutex) { //同步代码块 return c.size(); //c为被代理对象 } }
因为给所有的方法加上锁会降低代码的执行效率,而且有些方法是不需要加锁的,如果不想对所有的方法都加锁,可以在需要加锁的特定方法调用之前手动的做同步处理
下面进入ArrayList中的部分方法介绍,首先介绍从List父接口中继承过来的方法,其中使用到的[]代表对应参数可以存在也可以不存在,不过存在与不存在构成了不同的重载方法:
该方法用于向列表中添加元素,存在单参方法和双参两个重载方法,单参直接往列表末尾添加新元素,双参方法则可以手动指定插入到列表的哪个索引位置。每当列表元素的个数(size)超过列表容量(elementData.length)时,会触发grow方法(扩容方法),每次在老容量的基础上增加一半的容量,然后通过Arrays.copyOf方法进行新列表的创建和拷贝原列表内容
//在grow方法中关于新列表容量定义的关键代码 int newCapacity = oldCapacity + (oldCapacity >> 1);
该方法用于将另一个集合中的所有元素加入到当前列表中,该方法也存在单参和双参两个重载方法,单参方法把所有新元素添加到列表末尾,双参方法则在指定索引位置开始插入所有元素,扩容方法的触发和过程同add
该方法用于替换列表中指定索引位置的值,内部会先进行索引范围检查,返回值为被替换下的老元素
该方法用于判断列表中是否存在元素,返回值是一个布尔类型,源码的返回值为size == 0
该方法用于获取列表中指定位置的值并返回
该方法用于删除列表中特定索引位置的值,返回值为被删除的老元素
该方法用于删除列表中第一个为特定元素的值,可以传入null,代表删除列表中第一个为null的元素,返回值为布尔类型
该方法用于清空列表中的所有元素,内部对elementData列表对象的所有元素置空,并记录modCount(列表结构记录变量)值,最后将size置0
该方法用于删除集合中存在于传入列表中的所有指定元素,要注意的是,该方法为全列表查找删除,不同于remove方法只删除第一次出现的位置
该方法用于跟removeAll的作用刚好互补,用于保留传入集合中包含的所有指定元素,即删除指定集合之外的所有元素
该方法返回列表中元素的个数(size),但该值并不代表列表当前的容量(elementData.length)
该方法用于查询列表中特定元素出现的第一个索引位置,如果未找到则返回-1
该方法用于查询列表中特定元素出现的最后一个下标位置,如果未找到则返回-1
该方法用于判断列表中是否存在指定的元素,返回值为布尔类型,源码的返回值为indexOf(o) >= 0
该方法用于对列表元素按传入的指定排序规则进行排序,以下使用匿名内部类并结合Lambda表达式来作为示例构造一个二级排序规则,可根据需要增加更多层级的排序规则:
list.sort((o1,o2) -> { int result = -(o1.getClick() - o2.getClick());// 数值类型的比较写法,点击量降序 if (result == 0) {// 如果点击量相同,进入二级排序 result = o1.getDate().compareTo(o2.getDate());// 字符串类型的比较写法,时间升序 } return result;// 返回比较结果 });
该方法用于返回一个可操作性的子列表,不过要注意的是,子列表只不过是添加了偏移量的父列表,所以两个列表的对象是一致的,对于子列表中的操作,源码中都是在添加上对应的偏移量之后直接对父列表做对应修改,所以,对子列表的操作实际上就是对父列表的操作
该方法用于返回当前列表的一个普通迭代器,提供了hasNext,next,remove和forEachRemaining方法,其中hasNext用于判断是否存在下一个迭代对象,next用于将cursor指向下一个待操作元素,并返回当前元素(由内部的lastRet索引值进行指定),remove用于删除当前元素(该方法不可连续调用多次,因为内部的lastRet索引在一次操作后会被置为-1,可以会在下一次next时重新指向当前元素),forEachRemaining用于对列表未迭代对象执行传入的钩子方法
注意:如果在迭代器迭代对象时使用迭代器外部的remove或add方法改变了列表结构(新增或删除元素),会导致继续遍历列表时抛出ConcurrentModificationException异常,使用迭代器内部的remove方法改变列表结构不会抛出此异常
该方法用于返回一个加强版的迭代器对象,由于内部继承了iterator的类,所以可以提供iterator的所有功能,除此之外,还提供了自己独有的功能和特性,首先在初始化时就可以指定迭代初始索引,其次还提供了hasPrevious,nextIndex,previousIndex,previous,set和add方法,其中的previous方法可以实现列表的逆序遍历,set可以替换列表的当前迭代对象,add可以在当前迭代位置添加新的列表元素
注意:如果在迭代器迭代对象时使用迭代器外部的remove或add方法改变了列表结构(新增或删除元素),会导致继续遍历列表时抛出ConcurrentModificationException异常,使用迭代器内部的remove和add方法改变列表结构不会抛出此异常
该方法用于切割列表元素,可用于并发编程时多线程操作列表,需要先进行切割,方法内部默认对半分,再对切割完成的列表进行并行处理,示例代码如下:
ist list = new ArrayList(); list.add(1);list.add(2);list.add(3);list.add(4);list.add(5); list.add(6);list.add(7);list.add(8);list.add(9);list.add(0); //对列表进行切割 Spliterator<Integer> a = list.spliterator();//将列表转化为可拆分列表 Spliterator<Integer> b = a.trySplit();//将可拆分的a列表对半分 Spliterator<Integer> c = a.trySplit();//继续将可拆分的a列表对半分 Spliterator<Integer> d = b.trySplit();//将对半分得到的b列表对半分 //对切割好的列表进行操作 a.forEachRemaining(x -> System.out.print(x + " ")); System.out.println(); b.forEachRemaining(x -> System.out.print(x + " ")); System.out.println(); c.forEachRemaining(x -> System.out.print(x + " ")); System.out.println(); d.forEachRemaining(x -> System.out.print(x + " "));
打印结果为:
该方法用于将列表转化为数组,可以使用参数来指定生成数组的数据类型,返回值为指定类型的数组,以下为代码示例:
String[] strs=list.toArray(new String[0]);// list.toArray(new Object[0]);等同于list.toArray();
下面的方法是ArrayList中特有的方法,如果在你的代码中调用不到,请检查下声明该ArrayList对象的时候对象声明部分是否为ArrayList喔~
该方法继承自Cloneable接口父类,用于创建并返回一个浅克隆的列表对象
该方法用于检查列表容量是否已经达到瓶颈,继而判断是否需要进行扩容操作
该方法用于将列表容量调整至列表的元素总数,可以对不需要继续添加新元素的列表使用该操作用于释放部分资源,使用了该方法的列表,在添加新元素时会进行扩容操作,扩容方法依旧是扩容原容量的一半
用于对列表中的所有元素执行对应的操作,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:
list.forEach(x -> System.out.println(x.getAge()+1));// 如果只是简单的打印,还可以使用list.forEach(System.out::println);
该方法用于移除列表中符合条件的所有元素,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:
list.removeIf(x -> x.getAge() < 18);
如果对你有帮助,点个赞,或者打个赏吧,嘿嘿
整理不易,请尊重博主的劳动成果