ArrayList详解

一、介绍ArrayList是以什么数据结构实现的 

ArrayList底层的数据结构是顺序表。

顺序表:物理内存上连续、逻辑上连续、大小可以动态扩展

顺序表是由数组实现的,说道这里就理一下数组、链表、顺序表之间的关系。

逻辑结构:结构定义中是对操作对像的数学描述,描述的是数据元素之间的逻辑关系。例如,线性结构,树形结构,图状结构或网状结构。它们都属于逻辑结构。

物理结构:又称存储结构,是数据结构在计算机中的表示(又称映像)。例如,数组,指针。

线性表:属于逻辑结构中的线性结构,它包括顺序表和链表。

顺序表线性表中的一种,它是用数组来实现的一种线性表,所以它的存储结构(物理结构)是连续的。

链表:线性表中的一种,它的存储结构是用任意一组存储单元来存储数据元素。所以它的存储结构可以是连续的,也可以不是连续的。一般我们说的链表都是不连续的。有一种用数组来表示的链表,叫做静态链表,它的存储结构就是连续的。链表的逻辑结构上是连续的

数组:一种物理上连续的大小确定的存储结构

二、ArrayList源码分析

1. ArrayList是Java集合框架中的一个重要的类,它继承于AbstractList并且实现了List接口(其实本质就是Collection接口提供的增删改查和Collection继承的Iterable接口提供的Iterator功能),是一个长度可变的集合,提供了增删改查的功能。集合中允许null的存在。ArrayList类还是实现了RandomAccess接口,表明ArrayList⽀持快速(通常是固定时间)随机访问。在ArrayList中,我们可以通过元素的序号快速获取元素对象,这就是快速随机访问。实现了Serializable接口,说明ArrayList可以被序列化,还有Cloneable接口,实现了该接口,就可以使用Object.Clone()⽅法了。和Vector不同的是,ArrayList不是线程安全的。

2.ArrayList继承了AbstractList,而AbstractList已经实现了List接口,为什么还要实现List接口呢?

(1).这样代码更加灵活,ArrayList只用实现自己需要的方法就ok,不用把List接口里所有的方法都实现因为父类已经实现了

(2).如果ArrayList不实现List接口,就不能直接用来创建动态代理对象例:

package com.example;

import java.io.Serializable;
import java.util.Arrays;

public class Test {

    public static interface MyInterface {
        void foo();
    }

    public static class BaseClass implements MyInterface, Cloneable, Serializable {
        @Override
        public void foo() {
            System.out.println("BaseClass.foo");
        }
    }

    public static class Class1 extends BaseClass {
        @Override
        public void foo() {
            super.foo();
            System.out.println("Class1.foo");
        }
    }

    static class Class2 extends BaseClass implements MyInterface, Cloneable, Serializable {
        @Override
        public void foo() {
            super.foo();
            System.out.println("Class2.foo");
        }
    }

    public static void main(String[] args) {
        showInterfacesFor(BaseClass.class);
        showInterfacesFor(Class1.class);
        showInterfacesFor(Class2.class);
    }

    private static void showInterfacesFor(Class<?> clazz) {
        System.out.printf("%s --> %s\n", clazz, Arrays.toString(clazz.getInterfaces()));
    }
}

输出如下:

class example.Test$BaseClass --> [interface example.Test$MyInterface, interface java.lang.Cloneable, interface java.io.Serializable]
class example.Test$Class1 --> []
class example.Test$Class2 --> [interface example.Test$MyInterface, interface java.lang.Cloneable, interface java.io.Serializable]

我们看到Class1没有显式接口定义,所以Class#getInterfaces()没有包含这些接口,而Class2包含了这些接口。它的使用在下面这个程序才会清晰:

package example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import example.Test.BaseClass;
import example.Test.Class1;
import example.Test.Class2;

public class Test2 extends Test {

    public static void main(String[] args) {

        MyInterface c1 = new Class1();
        MyInterface c2 = new Class2();

        //注意顺序
        MyInterface proxy2 = createProxy(c2);
        proxy2.foo();
        //这个会因为unchecked exception而失败
        MyInterface proxy1 = createProxy(c1);
        proxy1.foo();
    }

    /**
     * 创建obj的代理类
     * @param obj 要被代理的对象
     * @return
     */
    private static <T> T createProxy(final T obj) {

        final InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.printf("要调用 %s 的 %s方法\n", obj, method.getName());
                return method.invoke(obj, args);
            }
        };

        return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}

输出如下:

要调用 example.Test$Class2@578ceb 的 foo方法
BaseClass.foo
Class2.foo
Exception in thread "main" java.lang.ClassCastException: $Proxy1 cannot be cast to example.Test$MyInterface
 at example.Test2.main(Test2.java:23)

虽然Class1隐式的实现了MyInterface,但是不能创建代理对象。

因此,如果我们想要获取具有隐式接口继承的对象的所有接口,从而来创建动态代理。那么唯一的办法就是通过反射遍历类的继承,并找出超类的所有接口。通过显式的声明接口,可以更容易获得该类继承的所有接口。

这个观点认为这样设置的原因是因为这样做能够方便动态创建代理对象。但我存疑的原因是:Collection框架是在jdk1.2的时候引入的,而Proxy是jdk1.3才加入的。这个回答只能解释了为什么这种形式会被保留下来,而不能说明作者当初为什么这么写。

3.源码中重要的字段和方法

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

要分配的数组的最大大小。 一些 VM 在数组中保留一些信息。 尝试分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超过 VM 限制

(1).elementData字段

transient Object[] elementData;

elementData用来存放ArrayList中的内容,可以看到是用Object数组存储的

(2).size字段

private int size;

size是element的个数注意是内容个数,不是elementData的长度

(3).EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA字段

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

两个空数组,在初始化的时候指定长度为0用EMPTY_ELEMENTDATA,DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个是无参构造默认返回,为什么用两个?

就是为了区分无参构造默认初始化长度为0和指定初始化长度为0的场景

(4).DEFAULT_CAPACITY字段

    private static final int DEFAULT_CAPACITY = 10;

数组走无参数构造,在使用时的最小默认长度为10

(5).参数为int值的构造方法

    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);
        }
    }

上面代码很好理解,给一个int值的长度就创建一个这个长度的Objec数组赋值给elementData,如果传的长度是0就直接把EMPTY_ELEMENTDATA这个字段赋值给elementData来初始化

(6).无参构造

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

无参构造直接把DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个赋值给elementData来初始化

(7).参数为集合Collection类型的构造方法

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }


// 还有一个SDK版本的构造方法和上面那个意思差不多:
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

函数首先将集合c转化为数组,然后检查转化的类型,如果不是Object[]类型,使用Arrays类中的copyOf方法进行复制;同时,如果c中没有元素,使用EMPTY_ELEMENTDATA初始化。

if (elementData.getClass() != Object[].class)

这一句什么时候会生效,首先我们搞清楚两个知识点:

1).因为泛型擦除ArrayList<Object> 和 ArrayList<String>的Class对象同一个即ArrayList<Object>.class == ArrayList<String>.class,但是string[].getClass() != object[].getClass()

2). 方法的返回值获取Class对象的时候返回的是实际类型

public class TestMain {
    public static void main(String[] args) {
        System.out.println(a().getClass());
    }

    public static Object a(){
        return 1;
    }
}

返回结果:

class java.lang.Integer

这是为什么呢,我理解的是多态,这个多态体现在a().getClass()的这个getClass方法上面,虽然getClass是一个final的方法在编译器可确定,但是它的执行也是通过invokeVirtual指令调用的(目的:为什么java中调用final方法是用invokevirtual指令而不是invokespecial? - 知乎),而invokeVirtual指令做的事情就是在执行的时候首先去判断调用者的实际类型,然后再去调用方法例:

public class ClassA {
    public void a(){
        System.out.println("A");
    }
}

public class ClassB extends ClassA{
    public final void a(){
        System.out.println("B");
    }
}

public class TestMain {
    public static void main(String[] args) {
       ClassB b = new ClassB();
       b.a();
       ClassA a = b;
       a.a();
    }

}

执行结果:

B
B

回到原题:if (elementData.getClass() != Object[].class)这一句什么时候会生效

实现 Collection接口的类可能会重写toArray();方法,有可能返回的就不是Object 数组了(elementData.getClass() != Object[].class) 是有可能成立的例:

public class ClassA implements Collection {

    @Override
    public String[] toArray() {
        return new String[1];
    }

//.....

}

public class TestMain {
    public static void main(String[] args) {
      ArrayList arrayList = new ArrayList(new ClassA());
      System.out.println(arrayList.toArray().getClass());
    }

}

(8).增

1).add(E e)

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

先分析ensureCapacityInternal(size + 1)方法:

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

如果是从无参构造方法构建的实例,就把size+1和10比较,取最大值,这里有一个疑问为啥只处理从无参构造初始化的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,不处理指定长度0初始化的EMPTY_ELEMENTDATA?

我的猜测是,如果走无参构造就按照默认的规则来即最小初始化长度为10,如果用户设置了初始化长度为0说明用户对内存占用率要求比较严苛,就按照真实内容的大小去申请elementData数组长度,这里也就解释了为啥定义两个长度为0的Object数组EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA

 例:使用无参数构造方法实例化:

public class TestMain {
    public static void main(String[] args) throws Exception {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("a");
        Class arrayListClass = arrayList.getClass();
        Field field = arrayListClass.getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] objects = (Object[]) field.get(arrayList);
        System.out.println(objects.length);
        System.out.println(Arrays.toString(objects));
    }
}

执行结果:

 使用参数为int值的构造方法,切设置参数为0:

public class TestMain {
    public static void main(String[] args) throws Exception {
        ArrayList<String> arrayList = new ArrayList<>(0);
        arrayList.add("a");
        Class arrayListClass = arrayList.getClass();
        Field field = arrayListClass.getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] objects = (Object[]) field.get(arrayList);
        System.out.println(objects.length);
        System.out.println(Arrays.toString(objects));
    }
}

执行结果: 

 下面继续分析add(E e):

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

modCount这个字段主要目的就是用来限制用户在迭代时修改列表,造成数据错乱,普通for循环不会抛异常但是会出现你不想要的结果,注意forEach本质是迭代器的语法糖所以用forEach循环也会抛出ConcurrentModificationException异常。例:

public class TestMain {
    public static void main(String[] args) throws Exception {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");
        for (int i = 0; i < arrayList.size(); i++) {
            System.out.println(arrayList.get(i));
            arrayList.remove(i);
        }

//       Iterator<String> iterator = arrayList.iterator();
//       while (iterator.hasNext()){
//           arrayList.remove(iterator.next());
//       }
    }
}

执行结果:

我们想的是把每个元素边打印边删除,可以因为在for循环过程中修改了ArrayList所以出现了意外

public class TestMain {
    public static void main(String[] args) throws Exception {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");
//        for (int i = 0; i < arrayList.size(); i++) {
//            System.out.println(arrayList.get(i));
//            arrayList.remove(i);
//        }

       Iterator<String> iterator = arrayList.iterator();
       while (iterator.hasNext()){
           arrayList.remove(iterator.next());
       }
    }
}

执行结果:

大家可以试一下forEach循环,这里就不在试了

迭代器具体是如何通过modCount限制的呢?

    private void checkForComodification() {
        if (this.modCount != l.modCount)
            throw new ConcurrentModificationException();
    }

(注意modCount的存在的意义是提供快速失败的迭代器,如果不维护这个modCount在迭代器迭代的过程中出现修改列表的行为,在单线程则不会出现这个异常) 例:

我自己搞了一个ArrayList

public class MyList<E> extends ArrayList<E> {


    @Override
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {
        }

        public boolean hasNext() {
            return cursor != size();
        }

        @SuppressWarnings("unchecked")
        public E next() {
//            checkForComodification();
            int i = cursor;
            if (i >= size())
                throw new NoSuchElementException();

            Object[] elementData = null;
            try {
                Field field = MyList.class.getSuperclass().getDeclaredField("elementData");
                field.setAccessible(true);
                elementData = (Object[]) field.get(MyList.this);
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                MyList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = MyList.this.size();
            int i = cursor;
            if (i >= size) {
                return;
            }
            Object[] elementData = null;
            try {
                Field field = MyList.class.getDeclaredField("elementData");
                field.setAccessible(true);
                elementData = (Object[]) field.get(MyList.this);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }


}

测试代码:

public class TestMain {
    public static void main(String[] args) throws Exception {
        MyList<String> arrayList = new MyList<>();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");

       Iterator<String> iterator = arrayList.iterator();
       while (iterator.hasNext()){
           String s = iterator.next();
           System.out.println(s);
           arrayList.remove(s);
       }
       
    }
}

执行结果:

我们不妨猜测一下,在修改列表行为发生时我们就记录一下修改的次数modCount例如:add()、remove()、addAll()、removeRange()及clear()方法,如果在循环迭代的时候发现这个modCount改变了说明列表被修改了就抛出ConcurrentModificationException异常。

在看源码前我们先深入了解一波迭代器Iterator:

Iterator的基本使用这里就不介绍了。

1).Iterator和Iterable什么关系

Iterator和Iterable都是接口,Iterable接口中有一个Iterator<T> iterator();方法,实现了Iterable就要实现自己的Iterator并且通过这个方法返回,换句话说Iterable提供了迭代器,实现了Iterable接口还可以使用语法糖forEach。Iterator就是一个接口,实现了Iterator接口就实现了迭代器的功能。

2).实现iterable的集合类内部都维护了自己的Iterator,我们在使用迭代器对集合进行迭代的时候一般都是直接使用集合类内部维护的迭代器

然后我们去看源码:

 private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

上面是ArrayList中的迭代器源码,可以看到expectedModCount 赋值为 modCount,在forEachRemaining(forEach语法糖)和next方法中都有调用checkForComodification检查modCount是否变动了,如果变动了在循环中就能直接快速的抛出异常,除了modCount检查,还有一句if (i >= elementData.length) throw new ConcurrentModificationException();这个有啥作用暂时没想到估计和多线程并发时的线程安全有关系。

那单线程怎么解决这个问题呢?

答:用Iterator自己的remove方法:

我们看一下它的remove方法做了什么特殊处理:

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                MyList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

expectedModCount = modCount 保证了下次modCount检查能通过

那多线程怎么解决这个问题呢?

public class Test {
    static ArrayList<Integer> list = new ArrayList<Integer>();
    public static void main(String[] args)  {
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Thread thread1 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    System.out.println(integer);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        };
        Thread thread2 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    if(integer==2)
                        iterator.remove(); 
                }
            };
        };
        thread1.start();
        thread2.start();
    }
}

执行结果:

原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。

因此一般有2种解决办法:

1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;

2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

 通过上面的分析我们知道了modCount是用来检测在迭代器迭代的时候是否发生对列表结构的改动,如果在迭代过程中列表发生了结构上的改变就抛出ConcurrentModificationException异常

上面说的就是fail-fast,快速失败机制

下面我们回到原题接着分析ensureExplicitCapacity这个方法:

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

if (minCapacity - elementData.length > 0)这个判断的意思是需要存放元素的最小长度比当前存放元素的数组的长度还大的时候这时候需要执行grow(minCapacity)进行扩容

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

int oldCapacity = elementData.length;这一句没啥好说的就是把原数组长度赋值给oldCapacity

int newCapacity = oldCapacity + (oldCapacity >> 1);这一句等价于

int newCapacity = oldCapacity + (oldCapacity / 2);想要把长度扩大为原来的1.5倍(注意还没有实际进行扩容操作)紧接着一个判断: 

 if (newCapacity - minCapacity < 0) newCapacity = minCapacity;

这句的意思是如果扩大为原来的1.5倍之后还是不够就直接直接它需要多少长度给多少长度,

然后判断一下如果扩容之后是否超过了Integer.MAX_VALUE - 8这个长度,超过了进行大容量分配:

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

不是说要保存一些头信息吗,怎么又把Integer.MAX_VALUE给最大长度了? 没想明白

最后进行实际的扩容操作:

elementData = Arrays.copyOf(elementData, newCapacity);

    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    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);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

先分析第一句:

T[] copy = ((Object)newType == (Object)Object[].class)
    ? (T[]) new Object[newLength]
    : (T[]) Array.newInstance(newType.getComponentType(), newLength);

判断原数组是不是Object数组如果是就创建一个扩容后的长度的Object数组,如果不是就通过type解析出泛型类型然后通过Array.newInstance创建出这个泛型类型的扩容后长度的数组。

然后分析第二句:

System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length)
代码解释:
  Object src : 原数组
   int srcPos : 从元数据的起始位置开始
  Object dest : 目标数组
  int destPos : 目标数组的开始起始位置
  int length  : 要copy的数组的长度

这个调用的是一个native本地的方法,就是一个把内容从原数组复制到目标数组的方法,最后把这个新创建的目标数组返回并且最后赋值给elementData这个数组,注意这时候只是扩容并把原来的数组内容copy到了扩容后的数组,还没有插入元素

回到add(E e)方法:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

上面我们分析完了ensureCapacityInternal(size + 1);

就剩下一句elementData[size++] = e; 可以从这句知道调用这个add(E e)方法添加元素最后会放在原有元素的后面,最后返回true表示添加成功

2).add(int index, E element)

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

先分析 rangeCheckForAdd方法

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

这个就是检查指针越界。

 ensureCapacityInternal(size + 1);  // Increments modCount!!这个我们已经分析过了,作用就是确保elementData内存足够存储add的数据,不够就去扩容。

System.arraycopy(elementData, index, elementData, index + 1,size - index);这一句就是要把我们要插入index位置和后面的数据全部往后移一位。

elementData[index] = element;这一句就是把数据插入到index的位置,并且ArrayList的size加1

越界检查有两个方法:

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

为啥一个是index >= size,一个是index > size。因为给add方法使用时如果添加在末尾,末尾的index值正好是size所以判断是index > size不包括size,而像查找、移除、修改只会在< size范围内所以判断是index >= size包括size,至于为啥rangeCheck没有判断index < 0 我认为这是一个错误

3).addAll(Collection<? extends E> c)

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

 Object[] a = c.toArray(); 把集合搞成数组

int numNew = a.length; 数组的长度赋值给numNew

ensureCapacityInternal(size + numNew);  // Increments modCount 确保elementData内存足够存储add的数据,不够就去扩容

System.arraycopy(a, 0, elementData, size, numNew); 把集合中的数据按原来的顺序copy到elementData数组的原内容的后面。

 size += numNew;维护集合的大小size

最后返回是否添加成功numNew != 0

4).addAll(int index, Collection<? extends E> c)

    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

其他的和上面差不多主要是这几句不一样:

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);

int numMoved = size - index;就是原数组从index之后的内容的长度

这几句的意思是,把原数组的index包括index和index之后的数据往后移要插入集合的长度,这段长度正好和集合长度相等,然后把集合中的内容按原来的顺序插入到index到index+插入的集合的长度这段内存中。

(9).删

1).remove(int index)

    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; // clear to let GC do its work

        return oldValue;
    }

rangeCheck(index);

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

检查指针是否越界

modCount++; 记录修改了列表结构,用来检测迭代器迭代中是否修改了列表结构
E oldValue = elementData(index); 

    E elementData(int index) {
        return (E) elementData[index];
    }

获取index这个位置的元素

int numMoved = size - index - 1; 计算index后面数组内容的长度不包括index所以需要-1

        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);

这一句就是直接把index后面的内容不包括index,向前移一位

elementData[--size] = null; 前面只是把后面的内容向前移了一位,用n+1的内容覆盖了n的内容,最后还有一个n+1在集合的逻辑size范围外没有任何实际意义但是底层数组还存储着它,所以需要处理成null, 这样它就可以被垃圾回收了,elementData数组最后一位索引正好是size - 1,所以采取了语法糖 --size 先计算然后再执行 elementData[index] = null。同时--size也维护了集合的size大小。

最后把删除的元素返回 return oldValue;

2).remove(Object o)

    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;
    }

因为ArrayList中可以存储null,所以这里判断是不是null来给存储的null做一个特殊处理,

接着循环查找这个元素,如果有就调用fastRemove方法删除:

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

这个方法和上面remove(int index)处理的方式完全一样,所以我们知道了remove(Object o)这个方法的本质就是循环查找然后如果有就确定下标,再和remove(int index)用一样的方式删除。

3). removeRange(int fromIndex, int toIndex)

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

modCount++;不说了

int numMoved = size - toIndex;  删除了从fromIndex到toIndex的元素后,toIndex后面的元素需要向前移动toIndex-fromIndex的距离,所以需要计算一下需要移动多少元素。

        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

这一句就是把toIndex后面的元素按原来的顺序向前移动toIndex-fromIndex的距离,其实就是从fromIndex那里按照原来的顺序开始排列

int newSize = size - (toIndex-fromIndex); 计算删除了fromIndex到toIndex的元素后还剩多少元素即新集合的长度

       for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }

把集合逻辑范围之外的数据置为null以便于垃圾回收

size = newSize;把集合的大小改成删除这段元素之后的大小

4).removeAll(Collection<?> c)

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

Objects.requireNonNull(c);这一句就是用来判空的:

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

重点分析batchRemove(Collection<?> c, boolean complement):

这个有个推荐文章:

ArrayList 里的 removeAll() 及 batchRemove() 方法【可能让你感受到jdk之美的文章】_System.out.print的博客-CSDN博客导语:removeAll(Collection&lt;?&gt; c) 方法不就是【从此列表中移除包含在指定集合中的所有元素】的意思吗! 这么见名之意,为啥还要讲?相信很多小伙伴儿都会有这个疑问。这样随意一个方法就写一篇文章,即浪费阅读者时间,也浪费csdn储存空间。说白了就想吐槽一句“垃圾文章!!!”。其实不然,之所以写下这篇文章,是因为 batchRemove(Collection&lt;?&...https://blog.csdn.net/weixin_40841731/article/details/85263889

 private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

先分析:

            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];

如果complement是false

代码的直接逻辑就是,把原数组(elementData)元素从第一个元素开始,如果参数数组(c)不包含这个元素就按顺序从原数组的0下标开始赋值存储起来。直到循环整个原数组

这个效果就是:把原数组中和参数数组一样的元素剔除,剔除的元素留空位,然后把剩下的数据收拢起来。

继续分析:

  // Preserve behavioral compatibility with AbstractCollection,
  // even if c.contains() throws.
  if (r != size) {
      System.arraycopy(elementData, r,
                       elementData, w,
                       size - r);
      w += size - r;
  }

这一段是为了处理异常,即如果是在c.contians()发生异常时导致没遍历完elementData导致r!=size,发生异常可能是空指针异常,或者类型转换异常,而且是发生在继承AbstractList但是没有重写contains()方法的实现类中,抛出异常后导致无法处理完, 此时r!=size, 所以需要把r右侧的元素保留下来。

最后分析:

if (w != size) {
    // clear to let GC do its work
    for (int i = w; i < size; i++)
        elementData[i] = null;
    modCount += size - w;
    size = w;
    modified = true;
}

if (w != size) 说明至少移除了一个元素

          // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;

把数组中集合逻辑大小额外的数据置为 null,这样这些数据就能倍垃圾回收了

modCount += size - w; 维护modCount修改次数,移除了多少个元素就修改了几次
size = w;  维护集合的大小
modified = true; 返回是否删除了至少一个元素

有一个问题,参数complement是干啥用的,它的作用就是原集合是剔除参数集合还是,剔除参数集合以外的元素,removeAll(Collection<?> c)这个方法是剔除原集合中和参数集合c中相等的元素,所以complement是false,还有一个函数是retainAll(Collection<?> c):

    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

它的作用就是把原集合中和参数集合相等的元素留下,其他的剔除掉。

5).clear()

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

这个就不解释了

(10).改

1).set(int index, E element)

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

rangeCheck(index);指针越界的检查

E oldValue = elementData(index); 这一句就是先把旧数据保存起来一会儿返回用

elementData[index] = element; 把旧值替换

最后把旧值返回

(11).查

1).get(int index)

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

这个越界检查后直接返回对应下标的元素

2).contains(Object o)、indexOf(Object o)

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

检查集合中是否存在这个元素:

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

因为ArrayList可以存储null,所以对null做了特殊处理,查找的方式就是循环遍历然后判断是否相等

查找到之后返回下标,查找的是第一个

3).isEmpty()

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

4).lastIndexOf(Object o)

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

也是对null做了特殊处理并且循环查找这个元素,只不过是倒着查找,查找到之后就返回下标所以查找的是最后一个

发现ArrayList里都是用的循环查找为什么不用二分法查找?

只有在有序的前提下才能用二分法,ArrayList的存储是无序的。

(12).还有一些常用的方法

1). clone()

clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?

1 使用new操作符创建一个对象
2 使用clone方法复制一个对象

那么这两种方式有什么相同和不同呢? new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

我们复习一下clone方法,clone方法是Object的一个方法:

protected native Object clone() throws CloneNotSupportedException;

使用:

一般我们在重写clone方法时会把它的修饰符改成public,Object的clone方法是被protected修饰的,如果不重写并且修改为public那就无法在外部调用。(改修饰符不是强制规定只是方便使用)

顺便复习一下权限修饰符: public(任何位置可以访问) > protected(本类、同包、有继承关系的子类) > 无修饰符(friendly、同包) > private(只能在本类)

如果不重写clone方法,由于无法做到和Object类同包,我们就先在继承类中使用:

public class Test{

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test = new Test();
        test.clone();
    }

}

运行结果:

为啥报错了,原来一个类要调用clone()方法,就要实现Cloneable接口否则会报CloneNotSupportedException 异常:

public class Test implements Cloneable{

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test = new Test();
        Test test1 = (Test) test.clone();
        System.out.println(test);
        System.out.println(test1);
    }

}

执行结果:

 还有一点要注意假如我们重写了clone方法就必须在clone()方法中调用super.clone(),这意味着无论clone类的继承结构是什么样的,都调用了java.lang.Object类的clone()方法。因为只有Object的clone方法才有clone的功能

深拷贝和浅拷贝:

这两者有什么区别:当拷贝遇到指针类型的时候如果只是把指针复制一份给新对象这个叫浅拷贝,如果重新创建一个指针类型的对象(内容一样)叫深拷贝

clone方法执行的是浅拷贝, 在编写程序时要注意这个细节证明:

public class Test implements Cloneable{

    public TestMain testMain = new TestMain();

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test = new Test();
        Test test1 = (Test) test.clone();
        System.out.println("原对象test:"  + test);
        System.out.println("clone对象test1:" + test1);
        System.out.println("原对象test属性testMain:" + test.testMain);
        System.out.println("clone象test1属性testMain:" + test1.testMain);
    }

}

执行结果:

 执行结果证明确实是浅拷贝。

如果想要在clone对象的时候进行深拷贝的话:

现在为了要在clone对象时进行深拷贝, 那么就要实现Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法得到新的对象, 还要将该类中所有的引用变量也clone出来。如果只是用Object中默认的clone方法,是浅拷贝的。

深拷贝:

public class Test implements Cloneable{

    public TestMain testMain = new TestMain();

    public static void main(String[] args) throws CloneNotSupportedException {
        Test test = new Test();
        Test test1 = (Test) test.clone();
        System.out.println("原对象test:"  + test);
        System.out.println("clone对象test1:" + test1);
        System.out.println("原对象test属性testMain:" + test.testMain);
        System.out.println("clone象test1属性testMain:" + test1.testMain);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Test test = (Test) super.clone();
        test.testMain = (TestMain) this.testMain.clone();
        return test;
    }
}

执行结果:

 推荐文章:

详解Java中的clone方法 -- 原型模式_昨夜星辰的博客-CSDN博客_clone()方法Java中对象的创建clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?1 使用new操作符创建一个对象2 使用clone方法复制一个对象那么这两种方式有什么相同和不同呢? new操作符的本意是分https://blog.csdn.net/zhangjg_blog/article/details/18369201ArrayList中的clone方法:

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

因为Object的clone方法是浅拷贝,而ArrayList需要进行深拷贝所以就有了这一句:

v.elementData = Arrays.copyOf(elementData, size);

然后维护一下modCount。

2). toArray()

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

这个方法没啥好讲的就是返回一个盒elementData内容一样的新数组,和上面clone的做法一样

3).writeObject序列化和readObject反序列化  感觉这个序列化需要出篇博客专门写

这里先给一篇别人的文章:

序列化简介:java序列化,看这篇就够了 - 9龙 - 博客园一、序列化的含义、意义及使用场景二、序列化实现的方式1、Serializable1.1 普通序列化1.2 成员是引用的序列化1.3 同一对象序列化多次的机制1.4 java序列化算法潜在的问题1.5https://www.cnblogs.com/9dragon/p/10901448.html序列化源码分析:

Java对象序列化底层原理源码解析_慕课手记https://www.imooc.com/article/257722

4).sort(Comparator<? super E> c)

    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

首先我们先搞清楚Comparator<? super E> c这个参数,

5).subList(int fromIndex, int toIndex)

为什么阿里巴巴要求谨慎使用ArrayList中的subList方法https://baijiahao.baidu.com/s?id=1637211558024016793&wfr=spider&for=pc

6).RandomAccess接口作用

RandomAccess是一个标记接口,没有任何实现,就是为了分类,一类是实现了RandomAccess接口的一类是没有实现RandomAccess接口的。

为啥要区分?因为底层由数组实现的集合在遍历的时候用for快,底层由链表实现的用Iterator快。

为啥数组用for遍历快?for循环的时候我们通过get方法取值,get方法可以直接用下标访问。为啥能用下标访问?因为数组中的类型都是一致,每一个元素占用的空间大小也就一致,可以用下标index通过计算来寻址。ArrayList的数组元素都是Object引用类型所以index就是下标。为啥数组用Iterator慢?ArrayList的Iterator遍历的实现主要是next方法,通过记录一个索引cursor每访问一个就cursor加1,本质和for没什么区别,但是next方法中还做了许多其他处理所以会慢一些,但是他们的时间复杂度都是O(n)

为啥链表用Iterator遍历快,用for循环遍历慢?用Iterator快是因为链表的Iterator取节点的时候会保存当前节点,Iterator的next方法取下一个的时候直接通过当前节点的后继(next)就能取到,这样循环一遍的时间复杂度是O(n)。如果用for循环,通过get(int index)方法取值,get方法是先判断index和链表长度的二分一比较即判断靠前还是靠后,然后通过for来从头或者尾节点开始遍历查找这一个get方法的时间复杂度就是O(n),然后外面还有一个for循环所以链表的for循环遍历的时间复杂度是O(n^2)

【Java】ArrayList 为啥要实现 RandomAccess 接口_九师兄-CSDN博客1.概述转载:http://www.javastack.cn/article/2019/why-arraylist-impl-randomaccess-interface/在我们的开发中,List接口是最常见不过,而且我们几乎每天都在用ArrayList或者LinkedList,但是细心的同学有没有发现,ArrayList中实现了RandomAccess接口,而LinkedList却没有实现RandomAccess接口,这是为什么呢?public class ArrayList<E> .https://blog.csdn.net/qq_21383435/article/details/108397282

三、提问

1.为什么size字段要序列化两次

在代码中s.defaultWriteObject();中size应该也被序列化了,为什么下边还要再单独序列化一次呢?

这样写是出于兼容性考虑。

旧版本的JDK中,ArrayList的实现有所不同,会对length字段进行序列化。

而新版的JDK中,对优化了ArrayList的实现,不再序列化length字段。

这个时候,如果去掉s.writeInt(size),那么新版本JDK序列化的对象,在旧版本中就无法正确读取,

因为缺少了length字段。

因此这种写法看起来多此一举,实际上却保证了兼容性。

附上官方解释:defaultReadObject() and defaultWriteObject() should be the first method call inside readObject(ObjectInputStream o) and writeObject(ObjectOutputStream o). It reads and writes all the non-transient fields of the class respectively. 

These methods also helps in backward and future compatibility. If in future you add some non-transient field to the class and you are trying to deserialize it by the older version of class then the defaultReadObject() method will neglect the newly added field, similarly if you deserialize the old serialized object by the new version then the new non transient field will take default value from JVM i.e. if its object then null else if primitive then boolean to false, int to 0 etc….

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值