ArrayList构造方法的源码暗藏玄机?

  • List
    接口的可调整大小的数组实现。

  • 数组:一旦初始化长度就不可以发生改变 。

数组结构介绍

  • 增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。

  • 查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。

源码中定义变量和常量

默认的初始化容量

private static final int DEFAULT_CAPACITY = 10;
复制代码

空数组没有默认容量

private static final Object[] EMPTY_ELEMENTDATA = {};
复制代码

默认容量的空数组

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
复制代码

集合真正存储数组元素的数组。 ArrayList
的底层数据结构, transient
表示该字段不进行序列化操作

transient Object[] elementData;
复制代码

ArrayList
的大小,就是集合中元素的个数

private int size;
复制代码

构造方法表格

Constructor Constructor描述
ArrayList() 构造一个初始容量为十的空列表。
ArrayList(int initialCapacity) 构造具有指定初始容量的空列表。
ArrayList(Collection<? extends E> c) 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。

空参构造 ArrayList()

源码解析

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码

案例演示

@Test
public void test_get(){
	// 运行这行代码真的会构造一个初始容量为10的集合吗?
	List<Integer> list = new ArrayList<>();
}
复制代码

验证是否构造一个初始容量为10的集合

@Test
public void test_get(){
   try {
      List<String> list = new ArrayList<>();
      Field field = list.getClass().getDeclaredField("elementData");
      field.setAccessible(true);
      int size = ((Object[]) field.get(list)).length;
      System.out.println(size);// 0 事实证明并没有初始化一个容量为10的集合
      // 添加第一个元素
      list.add("1");
      Field field2 = list.getClass().getDeclaredField("elementData");
      field2.setAccessible(true);
      int size2 = ((Object[]) field2.get(list)).length;
      System.out.println(size2);// 10 事实证明添加第一个元素的时候才进行初始化容量10
   } catch (Exception e) {
      e.printStackTrace();
   }
}
复制代码

结论:通过以上的代码演示证明空参构造方法创建集合对象并未构造一个初始容量为十的空列表,仅仅将DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址赋值给elementData。只有添加第一个元素的时候才会将容量初始化为10

指定容量 ArrayList(int initialCapacity)

源码解析

public ArrayList(int initialCapacity) {
    // 容量大于0,按照指定的容量初始化数组
    if (initialCapacity > 0) {
        // 创建一个数组,且指定长度为initialCapacity
        this.elementData = new Object[initialCapacity];
    // 如果initialCapacity容量为0,把EMPTY_ELEMENTDATA的地址赋值给elementData
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    // 容量小于0,抛非法异常    
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}
复制代码

代码演示

@Test
public void test_c(){
	// 创建一个带有初始容量的集合,这个集合创建以后真的初始容量为5吗?
	List<Integer> list = new ArrayList<>(5);
}
复制代码

验证是否构造一个我们指定初始容量5的集合

@Test
public void test_get(){
	try {
    // 构造一个长度为5的集合
		List<String> list = new ArrayList<>(5);
		Field field = list.getClass().getDeclaredField("elementData");
		field.setAccessible(true);
		int size = ((Object[]) field.get(list)).length;
		System.out.println(size);// 5 确实是我们自己指定的容量
	} catch (Exception e) {
		e.printStackTrace();
	}
}
复制代码

结论:根据 ArrayList
构造方法参数创建指定长度的数组。

按照集合迭代器返回的顺序,构造一个list包含指定集合的元素。c参数:元素将被放到list中的集合指定的集合为null,将会抛出 NullPointerException

指定集合 ArrayList(Collection<? extends E> c)

源码解析

public ArrayList(Collection<? extends E> c) {
    // 将给定的集合对象转成数组,且将数组的地址赋值给elementData
    elementData = c.toArray();
    // 将elementData的长度赋值给集合长度size,且判断是否不等于 0
    if ((size = elementData.length) != 0) {
        // 判断elementData 和 Object[] 是否为不一样的类型
        // c.toArray()数组不是object数组进行转换成Object[]
        // 每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下。
        // elementData.getClass()到底是什么类型的?下面搞个例子测试一下
        if (elementData.getClass() != Object[].class)
            // 转换Object[] 【在我的其他文章中的新增源码里面有分析】
            // 如果不一样,使用Arrays的copyOf方法进行元素的拷贝
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 给定的数组的长度为0,用空数组代替
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
复制代码

toArray()
方法及其实现源码

// Collection<E>接口中转数组接口
// 返回一个包含此集合中所有元素的数组。这个方法可以保证给定集合的顺序返回数组。此方法充当基于数组的API和基于集合的API之间的桥梁。
Object[] toArray();

// ArrayList<E>中的toArray()方法
public Object[] toArray() {
  // 调用数组工具类方法进行拷贝
	return Arrays.copyOf(elementData, size);
}
复制代码

Arrays类中的copyOf方法
源码

// Arrays类中的copyOf方法进行数组的拷贝。original原始的数组,newLength新的容量
public static <T> T[] copyOf(T[] original, int newLength) {
  // 再次调用方法进行拷贝
	return (T[]) copyOf(original, newLength, original.getClass());
}

// 将原始的数组copy到新的容量的数组中的具体实现
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
	@SuppressWarnings("unchecked")
  // 用三元运算符进行判断,不管结果如何都是创建一个新数组
	T[] copy = ((Object)newType == (Object)Object[].class)
			? (T[]) new Object[newLength]
			: (T[]) Array.newInstance(newType.getComponentType(), newLength);
	// 将数组的内容拷贝到 copy 该数组中,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位
  System.arraycopy(original, 0, copy, 0,
			Math.min(original.length, newLength));
  // 返回拷贝元素成功后的数组
	return copy;
}
复制代码

arraycopy
方法

/*
* @param      src      the source array.原始的数组
* @param      srcPos   starting position in the source array.在原始数组中开始的位置
* @param      dest     the destination array.目标数组
* @param      destPos  starting position in the destination data.在目标数组中的起始位置
* @param      length   the number of array elements to be copied.要copy的元素的个数
*/
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
复制代码

测试 elementData.getClass()
到底是什么类型

@Test
public void test_c(){
	List<Integer> list = new ArrayList<>();
	list.add(1);
  // getClass()这个方法返回的是该对象的运行时类。
	Class<? extends Object[]> aClass = list.toArray().getClass();
  System.out.println(aClass);// class [Ljava.lang.Object;
	if (aClass != Object[].class){
		System.out.println(false); 
	}else {
		System.out.println(true);// 打印结果:true
	}
}
复制代码

上面分析了半天的 Arrays.copyOf方法
接下来验证一下

@Test
public void test_arrays_copy_of(){
	Object[] str = new Object[]{"1", "2"};
  // 1代表的是要拷贝元素的个数
	Object[] arr_ = Arrays.copyOf(str, 1);
	System.out.println(Arrays.toString(arr_));
	// [1]

	Object[] str0 = new Object[]{"3", "4"};
	Object[] arr0_ = Arrays.copyOf(str0, 2);
	System.out.println(Arrays.toString(arr0_));
	// [3, 4]

	String[] str1 = new String[]{"5", "6"};
	String[] arr1_ = Arrays.copyOf(str1, 5);
	System.out.println(Arrays.toString(arr1_));
	//[5, 6, null, null, null] 元素不够会用null填充
}
复制代码

原文 

https://juejin.im/post/5efd9c246fb9a07ea2609299

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » ArrayList构造方法的源码暗藏玄机?

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址