如何实现Java的ArrayList经典实体类

ArrayList是Java集合框架中一个经典的实现类。他比起常用的数组而言,明显的优点在于,可以随意的添加和删除元素而不需考虑数组的大小。处于练手的目的,实现一个简单的ArrayList,并且把实现的过程在此记录。

实现的ArrayList主要的功能如下:

  • 默认构造器和一个参数的有参构造器
  • add方法
  • get方法
  • indexOf方法
  • contains方法
  • size方法
  • isEmpty方法
  • remove方法
  • sort方法

这个简单的ArrayList类 取名为SimpleArrayList,全部的代码查看SimpleArrayList代码

构造器

源码ArrayList一共有三个构造器,一个无参构造器,一个参数为int型有参构造器,一个参数为Collection型的有参构造器。参数为Collection型的构造器用来实现将其他继承Collection类的容器类转换成ArrayList。SimpleArrayList类因为还没有手动实现其他的容器类,所以实现的构造方法只有2个。代码如下:

 public SimpleArrayList(){
  this(DEFAULT_CAPACITY);
 }
 public SimpleArrayList(int size){
  if (size < 0){
   throw new IllegalArgumentException("默认的大小" + size);
  }else{
   elementData = new Object[size];
  }
 }

无参构造器中的 DEFAULT_CAPACITY是定义的私有变量,默认值是10,用来创建一个大小为10的数组。有参构造器中,int参数是用来生成一个指定大小的Object数组。将创建好的数组传给elementData。elementData是真正的用来存储元素的数组。

add方法

add 方法用来往容器中添加元素,add方法有两个重载方法,一个是add(E e),另一个是add(int index, E e)。add本身很简单,但是要处理动态数组,即数组大小不满足的时候,扩大数组的内存。具体的代码如下:

 public void add(E e){
  isCapacityEnough(size + 1);
  elementData[size++] = e;
 }

方法isCapacityEnough就是来判断是否需要扩容,传入的参数就是最小的扩容空间。因为add一个元素,所以最小的扩容空间,即新的长度是所有元素+ 1。这里的size就是真正的元素个数。

 private void isCapacityEnough(int size){
  if (size > DEFAULT_CAPACITY){
   explicitCapacity(size);
  }
  if (size < 0){
   throw new OutOfMemoryError();
  }
 }

判断扩容的方法也很简单,判断需要扩容的空间是不是比默认的空间大。如果需要的空间比默认的空间大,就调用explicitCapacity进行扩容。这里有个size小于0的判断,出现size小于0主要是因为当size超过Integer.MAX_VALUE就会变成负数。

 private final static int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
 private void explicitCapacity(int capacity){
  int newLength = elementData.length * 2;
  if (newLength - capacity < 0){
   newLength = capacity;
  }
  if (newLength > (MAX_ARRAY_LENGTH)){
   newLength = (capacity > MAX_ARRAY_LENGTH ? Integer.MAX_VALUE : MAX_ARRAY_LENGTH);
  }
  elementData = Arrays.copyOf(elementData, newLength);
 }

上面的代码是扩容的代码,首先,定义一个数组最大的容量的常量为最大值,这个值按照官方的源码中的解释是要有些VM保留了数组的头部信息在数组中,因此实际存放数据的大小就是整数的最大值 - 8

然后设定一个要扩容的数组的大小,虽然上面说了有一个扩容空间的值 size + 1 ,这个是实际我们最小需要扩容的大小。但为了继续增加元素,而不频繁的扩容,因此一次性的申请多一些的扩容空间。这里newLength 打算申请为 数组长度的2倍,然后去判断这个长度是否满足需要的扩容空间的值。 即有了后续的两段代码

  if (newLength - capacity < 0){
   newLength = capacity;
  }
  if (newLength > (MAX_ARRAY_LENGTH)){
   newLength = (capacity > MAX_ARRAY_LENGTH ? Integer.MAX_VALUE : MAX_ARRAY_LENGTH);
  }

如果2倍的长度仍然不满足,则申请到需要的扩容长度。在我们只增加一个元素的情况下,这个判断是永远不会生效的,但是如果有addAll方法,则增加的元素很多,就要导致一次申请2倍的长度是不够的。第二个判断是判断newLength的长度如果超过上面定义的数组最大长度则判断要需要的扩容空间是否大于数组最大长度,如果大于则newLength为 MAX_VALUE ,否则为 MAX_ARRAY_LENGTH。

最后,真正实现数组扩容到设定长度的方法就没意思了,调用Arrays.copyOf(elementData, newLength)得到一个扩容后的数组。

add的另一个重载方法也很简单。

 public void add(int index, E e) {
  //判断是不是越界
  checkRangeForAdd(index);
  //判断需不需要扩容
  isCapacityEnough(size + 1);
  //将index的元素及以后的元素向后移一位
  System.arraycopy(elementData,index,elementData,index + 1,size - index);
  //将index下标的值设为e
  elementData[index] = e;
  size++;
 }
 private void checkRangeForAdd(int index){
  //这里index = size是被允许的,即支持头,中间,尾部插入
  if (index < 0 || index > size){
   throw new IndexOutOfBoundsException("指定的index超过界限");
  }
 }

至此,一个简单的add方法就实现完了。

get方法

get方法用来得到容器中指定下标的元素。方法实现比较简单,直接返回数组中指定下标的元素即可。

 private void checkRange(int index) {
  if (index >= size || index < 0){
   throw new IndexOutOfBoundsException("指定的index超过界限");
  }
 }
 public E get(int index){
  checkRange(index);
  return (E)elementData[index];
 }

indexOf方法

indexOf方法用来得到指定元素的下标。实现起来比较简单,需要判断传入的元素,代码如下:

 public int indexOf(Object o){
  if (o != null) {
   for (int i = 0 ; i < size ; i++){
    if (elementData[i].equals(0)){
     return i;
    }
   }
  }else {
   for (int i = 0 ; i < size ; i++){
    if (elementData[i] == null) {
     return i;
    }
   }
  }
  return -1;
 }

判断传入的元素是否为null,如果为null,则依次与null。如果不为空,则用equals依次比较。匹配成功就返回下标,匹配失败就返回-1。

contains方法

contains用来判断该容器中是否包含指定的元素。在有了indexOf方法的基础上,contains的实现就很简单了。

  public boolean contains(Object o){
  return indexOf(o) >= 0;
  }

size方法

size方法用来得到容器类的元素个数,实现很简单,直接返回size的大小即可。

 public int size(){
  return size;
 }

isEmpty方法

isEmpty方法用来判断容器是否为空,判断size方法的返回值是否为0即可。

 public boolean isEmpty(){
  return size() == 0;
 }

remove方法

remove方法是用来对容器类的元素进行删除,与add一样,remove方法也有两个重载方法,分别是

remove(Object o)和remove(int index)

  public E remove(int index) {
  E value = get(index);
  int moveSize = size - index - 1;
  if (moveSize > 0){
   System.arraycopy(elementData,index + 1, elementData,index,size - index - 1);
  }
  elementData[--size] = null;
  return value;
 }

 public boolean remove(Object o){
  if (contains(o)){
   remove(indexOf(o));
   return true;
  }else {
   return false;
  }
 }

第一个remove方法是核心方法,首先得到要删除的下标元素的值,然后判断index后面的要前移的元素的个数,如果个数大于零,则调用库方法,将index后面的元素向前移一位。最后elementData[--size] = null;缩减size大小,并将原最后一位置空。

第二个remove方法不需要向第一个方法一样,需要告诉使用者要删除的下标对应的元素,只需要判断是否删除成功即可。如果要删除的元素在列表中,则删除成功,如果不在则失败。因此调用contains方法就可以判断是否要删除的元素在列表中。在则调用remove(int index),不在则返回失败。

总结

自此,一个简单的ArrayList就实现完了,实现的目的是为了弄清ArrayList动态数组的原理以及add与remove方法的内容实现。同时,也清楚了ArrayList最大的扩容空间就是Integer的最大值。该类的所有代码在SimpleArrayList代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

(0)

相关推荐

  • Java ArrayList的不同排序方法

    由于其功能性和灵活性,ArrayList是 Java 集合框架中使用最为普遍的集合类之一.ArrayList 是一种 List 实现,它的内部用一个动态数组来存储元素,因此 ArrayList 能够在添加和移除元素的时候进行动态的扩展和缩减.你可能已经使用过 ArrayList,因此我将略过基础部分.如果你对 ArrayList 还不熟悉,你可以参考它的 API 文档,可以很容易理解在 ArrayList 上执行基本的操作. 在这篇文章中,我将讨论 ArrayList 中一种极其重要的操作,你很

  • java中ArrayList 、LinkList的区别分析

    1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.      2.对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处.(参考数组与链表来思考)     3.对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间. ArrayList和LinkedL

  • java ArrayList集合中的某个对象属性进行排序的实现代码

    开发中有时候需要自己封装分页排序时,List如何对某一属性排序呢,分享一个小实例,大家共勉,希望能对大家有用,请多多指教. 1.Student的Bean如下: public class Student { private int age; private String name; private String weight; public String getWeight() { return weight; } public void setWeight(String weight) { th

  • JAVA ArrayList详细介绍(示例)

    第1部分 ArrayList介绍ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口.ArrayList 继承了AbstractList,实现了List.它是一个数组队列,提供了相关的添加.删除.修改.遍历等功能.ArrayList 实现了RandmoAccess接口,即提供了随机访问功能.Randmo

  • Java中ArrayList类的使用方法

    Java中ArrayList类的用法 1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和IList接口 灵活的设置数组的大小 2.如何使用ArrayList 最简单的例子: ArrayList List = new ArrayList(); for( int i=0;i <10;i++ ) //给数组增加10个Int元素 List.Add(i); //..

  • 由ArrayList来深入理解Java中的fail-fast机制

    1. fail-fast简介 "快速失败"也就是fail-fast,它是Java集合的一种错误检测机制.某个线程在对collection进行迭代时,不允许其他线程对该collection进行结构上的修改. 例如:假设存在两个线程(线程1.线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生f

  • 详解Java中Vector和ArrayList的区别

    首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复. 3个具体实现类的相关区别如下: 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对

  • Java Array与ArrayList区别详解

    Java Array与ArrayList区别 1)精辟阐述: 可以将 ArrayList想象成一种"会自动扩增容量的Array". 2)Array([]):最高效:但是其容量固定且无法动态改变: ArrayList:  容量可动态增长:但牺牲效率: 3)建议: 基于效率和类型检验,应尽可能使用Array,无法确定数组大小时才使用ArrayList! 不过当你试着解决更一般化的问题时,Array的功能就可能过于受限. 4)Java中一切皆对象,Array也是对象.不论你所使用得Array

  • Java ArrayList.toArray(T[]) 方法的参数类型是 T 而不是 E的原因分析

    前两天给同事做 code review,感觉自己对 Java 的 Generics 掌握得不够好,便拿出 <Effective Java>1 这本书再看看相关的章节.在 Item 24:Eliminate unchecked warnings 这一节中,作者拿 ArrayList 类中的 public <T> T[] toArray(T[] a) 方法作为例子来说明如何对变量使用 @SuppressWarnings annotation. ArrayList 是一个 generic

  • Java中Vector与ArrayList的区别详解

    首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复.3个具体实现类的相关区别如下:1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对数组

随机推荐