探索 ArrayList 原理 - 第二节 ArrayList 继承体系源码分析

探索 ArrayList 原理

jdk1.8 API
黑马教学视频: java进阶教程丨全面深入解析ArrayList原理(源码分析+面试讲解)

2. ArrayList 继承体系

  • 源码结构图如下
    在这里插入图片描述
  • ArrayList主要实现了List接口,同时标记为可以序列化Serializable、可复制CloneAble、支持随机访问RandomAccess。

3. ArrayList 继承体系源码分析

  • ArrayList
    public class ArrayList<E> extends AbstractList<E>
         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
3.1 Serializable 接口
  • 序列化
    • 1、将一个Java对象变成字节序列,方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,
    • 2、变换成字节序列也更便于网络运输和传播。
    • 3、序列化机制从某种意义上来说也弥补了平台化的一些差异,毕竟转换后的字节流可以在其他平台上进行反序列化来恢复对象,因此可以实现跨平台存储。
  • Serializable 接口,就是序列化接口,实现此接口的类将不会使任何状态序列化或反序列化;
    • 序列化: 将 对象 的数据写入到文件(写对象
    • 反序列化: 将文件中 对象 的数据读取出来
    • 序列化
      • 示例如下:写对象示例 注意必须传一个对象,序列化也指的就是序列化对象
      • ApeEntity 实例
           @NoArgsConstructor
        @AllArgsConstructor
        @Data
        public class ApeEntity {
        
           private String id;
        
           private String sbmc;
        
           private String sblx;
        
           private String ipdz;
        }
        
      • 测试写入文件
            public static void main(String[] args) throws IOException {
                ApeEntity ape1 = new ApeEntity("1001","天地","天地","192.168.1.2");
                ApeEntity ape2 = new ApeEntity("1002","海康","海康","192.168.1.2");
                ApeEntity ape3 = new ApeEntity("1003","大华","大华","192.168.1.2");
                ArrayList<ApeEntity> list = new ArrayList<>();
                list.add(ape1);
                list.add(ape2);
                list.add(ape3);
                writeObjStream("D:/temp/exchange_snmp1.log", list);
            }
            public static void writeObjStream(String filepath, ArrayList<ApeEntity> list) throws IOException {
            
            FileOutputStream outputStream = new FileOutputStream(filepath);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(list);
            objectOutputStream.close();
        }
        
      • 实体对象 ApeEntity 未实例化 ,执行上面代码报错;
            Exception in thread "main" java.io.NotSerializableException: arrayList.ApeEntity
           	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
           	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
           	at java.util.ArrayList.writeObject(ArrayList.java:766)
           	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
           	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
           	at java.lang.reflect.Method.invoke(Method.java:498)
           	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
           	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
           	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
           	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
           	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
           	at arrayList.source.ArrayListSerializableDemo.writeObjStream(ArrayListSerializableDemo.java:77)
           	at arrayList.source.ArrayListSerializableDemo.main(ArrayListSerializableDemo.java:28)
        
      • ApeEntity 实例序列化
            @NoArgsConstructor
           @AllArgsConstructor
           @Data
           public class ApeEntity implements Serializable {
           
               private String id;
           
               private String sbmc;
           
               private String sblx;
           
               private String ipdz;
           }
        
        • 序列化后效果,在目的位置生成了文件
          -
    • 反序列化
    • 示例代码如下:
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              readObjStream("D:/temp/exchange_snmp1.log");
          }
          public static void readObjStream(String filepath) throws IOException, ClassNotFoundException {
              FileInputStream fileInputStream = new FileInputStream(filepath);
              ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
              List<ApeEntity> list = (List<ApeEntity>) objectInputStream.readObject();
              objectInputStream.close();
              System.out.println(list.toString());
          }
      
      • 反序列化执行效果,
        Connected to the target VM, address: '127.0.0.1:50838', transport: 'socket'
        [ApeEntity(id=1001, sbmc=天地, sblx=天地, ipdz=192.168.1.2), ApeEntity(id=1002, sbmc=海康, sblx=海康, ipdz=192.168.1.2), ApeEntity(id=1003, sbmc=大华, sblx=大华, ipdz=192.168.1.2)]
        Disconnected from the target VM, address: '127.0.0.1:50838', transport: 'socket'
        
3.2 CloneAble 接口
  • 一个类实现CloneAble 接口来指示Object.clone() 方法,该方法对于该类的实例进行字段的复制是合法的。

  • 简而言之:克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝

  • 克隆的前提条件:

    • 1、被克隆对象所在的类必须实现 CloneAble 接口;
    • 2、必须重写clone方法;
  • 源码分析:

        public Object clone() {
            try {
               // 调用父类(Object)的clone()方法,克隆一个object对象,然后强转为ArrayList
                ArrayList<?> v = (ArrayList<?>) super.clone();
                  // 调用Arrays类的copyOf,将ArrayList的elementData数组赋值给副本的elementData数组 
                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);
            }
    }
    
  • clone 的基本使用

        public static void main(String[] args)  {
            Student student1 = new Student("xiaoming1","man",0);
            Student student2 = new Student("xiaoming2","man",0);
            Student student3 = new Student("xiaoming3","man",0);
    
            ArrayList<Student> stuList = new ArrayList<>();
            stuList.add(student1);
            stuList.add(student2);
            stuList.add(student3);
    
            ArrayList<Student> stuList2  = (ArrayList)stuList.clone();
            for (Student stu:stuList2
                 ) {
                System.out.println(stu);
            }
    
            System.out.println(stuList == stuList2);
        }
    

    示例结果

    	Student(name=xiaoming1, sex=man, age=0)
    	Student(name=xiaoming2, sex=man, age=0)
    	Student(name=xiaoming3, sex=man, age=0)
    	false
    
  • 案例分析:

    • 已知学生对象{姓名:小明,性别 男,年龄10},现需要将学生小明复制到另一个对象中去,且两个之间互相不影响
      • 方法一:
            public  static  void test1(){
                Student stu1 = new Student("小明","男",10,null);
                Student stu2 = new Student();
                stu2.setName(stu1.getName());
                stu2.setSex(stu1.getSex());
                stu2.setAge(stu1.getAge());
            }
        
      • 方法二 克隆:注意student 需要实现 Cloneable,并重写clone方法;
        • studet.java
                  @Data
          		@AllArgsConstructor
          		@NoArgsConstructor
          		public class Student Cloneable{
          		    private String name;
          		    private String sex;
          		    private Integer age;						
          		    @Override
          		    public Object clone() throws CloneNotSupportedException {
          		        return super.clone();
          		    }
          		}
          
        • 克隆
                  Student stu1 = new Student("小明","男",10);
                  Object clone = stu1.clone();
                  System.out.println(stu1 == clone);
                  System.out.println(stu1);
                  System.out.println(clone);
                  stu1.setAge(100);
                  System.out.println(stu1);
                  System.out.println(clone);
          
          示例结果如下
          	false
          	Student(name=小明, sex=, age=10)
          	Student(name=小明, sex=, age=10)
          	Student(name=小明, sex=, age=100)
          	Student(name=小明, sex=, age=10)
          

        以上实例简单使用浅克隆,完全可以满足需求,如果说student 中还内嵌对象,如学生中还技能对象,那么浅克隆就满足不了了;

        • 如下学生类示例
          public class Student implements Serializable ,Cloneable{
              private String name;
              private String sex;
              private Integer age;
              private Skill skill; //技能,特长
          
              @Override
              public Object clone() throws CloneNotSupportedException {
                  return super.clone();
              }
          }
          
          技能,特长类
          public class Skill {
              private  String name;
          }
          
        • 示例代码如下
              public static void test4() throws CloneNotSupportedException {
                  Student stu1 = new Student();
                  stu1.setName("小明");
                  stu1.setSex("男");
                  stu1.setAge(10);
                  Skill skill = new Skill("跆拳道");
                  stu1.setSkill(skill);
                  Object clone = stu1.clone();
                  System.out.println(stu1 == clone);
                  System.out.println(stu1);
                  System.out.println(clone);
                  stu1.setAge(100);
          
                  skill.setName("武术");
                  stu1.setSkill(skill);
                  System.out.println(stu1);
                  System.out.println(clone);
              } 
          
        • 示例代码效果如下
          		false
          		Student(name=小明, sex=, age=10, skill=Skill(name=跆拳道))
          		Student(name=小明, sex=, age=10, skill=Skill(name=跆拳道))
          		Student(name=小明, sex=, age=100, skill=Skill(name=武术))
          		Student(name=小明, sex=, age=10, skill=Skill(name=武术))
          

          当修改学生的值时,影响到了克隆出来学生的值 ,此时浅克隆已经满足不了需求,这时我们需要用到深克隆

        • 深克隆
          • 技能、特长类实现 Cloneable
            public class Skill implements Cloneable{
                private  String name;
            
                @Override
                protected Object clone() throws CloneNotSupportedException {
                    return super.clone();
                }
            }
            
          • 学生类重写 Clone 方法
            	public class Student implements Serializable ,Cloneable{
            	    private String name;
            	    private String sex;
            	    private Integer age;
            	    private Skill skill;
            	
            	    @Override
            	    public Object clone() throws CloneNotSupportedException {
            	        //深克隆不能简单的调用父类的方法
            	
            	        //引用super.clone 先克隆出一个 object,然后向下转型
            	        Student stu = (Student)super.clone();
            	        Skill skill = (Skill)this.skill.clone();
            	        stu.setSkill(skill);
            	        return stu;
            	    }
            	
            	}
            
        • 直接还是用 test4() 示例测试,效果如下
          	false
          	Student(name=小明, sex=, age=10, skill=Skill(name=跆拳道))
          	Student(name=小明, sex=, age=10, skill=Skill(name=跆拳道))
          	Student(name=小明, sex=, age=10, skill=Skill(name=武术))
          	Student(name=小明, sex=, age=10, skill=Skill(name=跆拳道))
          

          相互之间已经做到了互不影响

3.3 RandomAccess 接口
  • 标记接口由List实现使用,以表明它们支持快速(通过为恒定时间)随机访问
  • 此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表顺序访问列表时提供良好的性能
  • 需要值得让人注意的是,List 实现了此接口,如果对于类的典型实例,此种方式比迭代器实例(iterator)更快
    • 典型实例
      	for(int i = 0 , n = list.size(); i< n ;i++) 
      	  list.get(i);
      
    • 迭代器实例
      	for(Iterator i=list.iterator(); i.hasNext();) 
      	  i.next();
      
  • 案例演示1 ArrayList 随机访问列表顺序访问列表哪种方式更快
        public static void test(){
            ArrayList<Integer> stuList = new ArrayList<>();
            for(int i = 0;i < 100000 ; i++){
                stuList.add(i);
            }
    		//测试随机访问时间
            long startMillis = System.currentTimeMillis();
            for (int i = 0 ; i< stuList.size();i++) {
                stuList.get(i);
            }
            long endMillis = System.currentTimeMillis();
            System.out.println("ArrayList-随机访问列表耗时:"+(endMillis - startMillis));
    
    	    //测试顺序访问时间
            Iterator<Integer> iterator = stuList.iterator();
            startMillis = System.currentTimeMillis();
            while (iterator.hasNext()){
                 iterator.next();
            }
            endMillis = System.currentTimeMillis();
            System.out.println("ArrayList-顺序访问列表耗时:"+(endMillis - startMillis));
        }
    
    示例结果如下
    ArrayList-随机访问列表耗时:1
    ArrayList-顺序访问列表耗时:3
    

    因为ArrayList 是实现了RandomAccess 接口的,所以它会比Iterator 快;

  • 案例演示2 LinkList 随机访问列表顺序访问列表哪种方式更快
        public static void test2(){
            LinkedList<Integer> stuList = new LinkedList<>();
            for(int i = 0;i < 100000 ; i++){
                stuList.add(i);
            }
    
            long startMillis = System.currentTimeMillis();
            for (int i = 0 ; i< stuList.size();i++) {
                stuList.get(i);
            }
            long endMillis = System.currentTimeMillis();
            System.out.println("LinkedList-随机访问列表耗时:"+(endMillis - startMillis));
    
            Iterator<Integer> iterator = stuList.iterator();
            startMillis = System.currentTimeMillis();
            while (iterator.hasNext()){
                iterator.next();
            }
            endMillis = System.currentTimeMillis();
            System.out.println("LinkedList-顺序访问列表耗时:"+(endMillis - startMillis));
        }
    
    示例结果如下
    	LinkedList-随机访问列表耗时:4581
    	LinkedList-顺序访问列表耗时:1
    

    因为LinkedList 是没有RandomAccess 接口的,所以它会比随机访问更快;还有需要值 得注意的时候,在添加数据的时候我们很明显能看出来linkedList 添加数据是比较慢的:
    linkedList 添加数据是比较慢原因:当数据量大时,ArrayList每次扩容都能得到很大的新空间,解决了前期频繁扩容的劣势,而LinkedList虽然有尾指针,但是每次add都要将对象new成一个Node(而ArrayList直接数组对应位置元素赋值)

3.3.1 ArrayList 实际开发应用场景应用
  • 基于实现 RandomAccess接口之后,随机访问列表比顺序访问列表效率更高,所以建议当查询出结果之后进行判断是否实现了接口RandomAccess,然后判断自己使用哪种方式的遍历;
  • 直接上判断代码如下:
      //模拟从数据库查询出数据结果
      List<User> list = dao.query(example);
    
      //判断
      if(list.instanceof RandomAccess){
          //随机访问
         for(int i = 0; i < list.size(); i++){
         	……
         }
      }else{
      	//顺序访问
      	for(User user:List){
      		……
      	}
      }
    
3.4 ArrayList 抽象类
  • ArrayList extends AbstractList

    • AbstractList extends AbstractCollection
        java中所有类都继承 Object,如下图 ArrayList的继承结构。
        在这里插入图片描述
  • AbstractList是一个抽象类,实现了List接口,List定义了一些List通用方法,而AbstractList抽象类中可以有抽象方法,还可以有具体的实现方法, AbstractList实现接口中一些通用的方法,实现了基础的add/get/indexOf/iterator/subList/RandomAccessSubList方法,ArrayList再继承AbstractList,拿到通用基础的方法,然后自己在实现一些自己特有的方法,这样的好处是:让代码更简洁,继承结构最底层的类中通用的方法,减少重复代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值