ArrayList中标记性接口介绍----Serializable、Cloneable、RandomAccess

0. 前言

  1. 通过这篇文章你可以了解Serializable、Cloneable、RandomAccess这些标记性接口的作用。
  2. 你可以了解到什么是浅拷贝、什么是深拷贝。
  3. 什么是随机访问、什么是顺序访问

1. Serializable序列化接口

  1. 序列化接口没有方法或字段,仅仅用于标识可串行化的语义。

  2. 类的序列化由实现Serializable接口的类启动。不实现此接口的类将不会使用任何状态序列化或者反序列化

  3. 序列化:将对象的数据写入到文件(写对象)

  4. 反序列化:将文件中对象的数据读取出来(读对象)

  5. 代码例子:

    import java.io.*;
    
    /**
     * @author JIN
     * @description 序列化
     * @createTime 2021-03-03 18:27
     **/
    public class Test1 {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Person p = new Person("小明",18);
    
            // 序列化 写对象
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/data1.txt"));
            out.writeObject(p);
    
            // 反序列化  读对象
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("src/data1.txt"));
            Person object = (Person) in.readObject();
            System.out.println(object);
        }
    }
    // 一定要实现Serializable接口
    class Person implements Serializable {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    

2. Cloneable克隆标记性接口: 用于克隆

  1. 一个类实现Cloneable接口来指示Object.clone()方法,该方法对于该类的实例进行字段的复制是合法的

  2. 不现实Cloneable接口的实例上调用对象的克隆方法会导致异常CloneNotSupportedException被抛出.

  3. 克隆就是依据有的数据,创造一份新的完全一样的数据拷贝

  4. 克隆的前提条件

    1. 被克隆对象所在的类必须实现Cloneable接口
    2. 必须重写clone方法
  5. 代码例子:

    public class CloneableTest {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
    
            Object clone = list.clone();
            System.out.println(clone); // [a, b, c]
            // 说明只是复制了数据内容。
            System.out.println(clone == list); // false
        }
    }
    

3. 传统方法拷贝

  1. 题目:现有一个User对象[name=“小明”,age=18],需要得到一个与之数据相同的对象User2.

  2. 传统代码:

    /**
     * @author JIN
     * @description
     * @createTime 2021-03-03 18:46
     **/
    public class User {
        private String name;
        private int age;
    
        public User() {
        }
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    public class Test2 {
        public static void main(String[] args) {
            User user1 = new User("小明",18);
    		// 直接将user1的值赋给user2,但如果属性很多,十几个的时候,我们就觉得很繁琐
            User user2 = new User();
            user2.setName(user1.getName());
            user2.setAge(user1.getAge());
            System.out.println(user2);
            
           	// 修改user1的属性,查看user2是否会改变---不会改变
            user1.setAge(2000);
            System.out.println(user);// User{name='小明', age=2000}
            System.out.println(cloneUser); // User{name='小明', age=18}
        }
    }
    

4. 浅拷贝与深拷贝

  1. 克隆的前提条件

    1. 被克隆对象所在的类必须实现Cloneable接口
    2. 必须重写clone方法
  2. 浅拷贝的实现方法:

    // 修改User类
    public class User implements Cloneable{
        // 重写方法的时候,把方法的权限修饰符更改为public
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    	// 其他代码不变
    }
    
    public class Test2 {
        public static void main(String[] args) throws CloneNotSupportedException {
            User user1 = new User("小明",18);
    
            // 代码明显比传统方法简单很多
            User cloneUser = (User) user1.clone();
            System.out.println(cloneUser); //User{name='小明', age=18}
            
            // 修改user1的属性,查看user2是否会改变---不会改变
            user1.setAge(2000);
            System.out.println(user);// User{name='小明', age=2000}
            System.out.println(cloneUser); // User{name='小明', age=18}
        }
    }
    

    但是浅拷贝也有自己的局限性基本数据类型可以达到完全复制,但是引用数据类型则不可以,对于引用数据类型来说仅仅是拷贝了一份引用。即若对引用数据类型进行修改,拷贝哪一方也会一起修改。

    代码演示

    public class School {
        private String name;
    
        public School(String name) {
            this.name = name;
        }
        
        @Override
        public String toString() {
            return "School{" +
                    "name='" + name + '\'' +
                    '}';
        }
        
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
    public class User implements Cloneable{
    
        private String name;
        private int age;
        // 添加一个引用数据类型
        private School school;
    
        public User(String name, int age, School school) {
            this.name = name;
            this.age = age;
            this.school = school;
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", school=" + school +
                    '}';
        }
    
        public User() {
        }
    
        public School getSchool() {
            return school;
        }
    
        public void setSchool(School school) {
            this.school = school;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    
    public class Test2 {
        public static void main(String[] args) throws CloneNotSupportedException {
            User user1 = new User("小明",18,new School("北京"));
    
            User cloneUser = (User) user1.clone();
            System.out.println(cloneUser);
            System.out.println("---------------");
            // 修改user1的学校,查看cloneUser是否会改变----会一起变
            user1.getSchool().setName("上海");
            System.out.println("user1: " + user1);
            System.out.println("cloneUser: " + cloneUser);
        }
    }
    
    User{name='小明', age=18, school=School{name='北京'}}
    ---------------
    user1: User{name='小明', age=18, school=School{name='上海'}}
    cloneUser: User{name='小明', age=18, school=School{name='上海'}}
    

    从上面结果,我们看到了当user1修改了学校的名字,cloneuser中学校的名字也一起改变了,显然这不是我们想要的结果,我们只是想要数据的内容的复制就好了,不需要有关系、关联。

    原因在学生对象user被克隆的时候,其属性School(引用类型)仅仅是拷贝了一份引用,因此在School的值发生改变的时候,被克隆对象cloneuser的属性也会改变。

    解决方案:使用深拷贝

  3. 深拷贝代码:

    针对上面浅拷贝的问题,我们并不能简单的调用父类的clone,需要自己重写clone中代码。

    // 对于school类,修改如下:
    public class School implements Cloneable{
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
     
        // 其他不变
    }
    
    // 对于user类,修改如下:
    public class User implements Cloneable{
        
        @Override
        public Object clone() throws CloneNotSupportedException {
            // 1. 先克隆出一个user对象
            User cloneUser = (User) super.clone();
            // 2. 再克隆出一个引用类型的对象
            School cloneSchool = (School) this.school.clone();
            // 3. 将引用类型的新对象赋值给克隆出来的cloneUser
            cloneUser.setSchool(cloneSchool);
            // 克隆出来的cloneSchool相当于新的一个对象,与原来的school对象并没用关联。
            // 4. 返回克隆对象
            return cloneUser;
        }
     
        // 其他不变
    }
    
    public class Test2 {
        public static void main(String[] args) throws CloneNotSupportedException {
            User user1 = new User("小明",18,new School("北京"));
    
            User cloneUser = (User) user1.clone();
            System.out.println(cloneUser);
            System.out.println("---------------");
            // 修改user1的学校,查看cloneUser是否会改变----不会一起变
            user1.getSchool().setName("上海");
            System.out.println("user1: " + user1);
            System.out.println("cloneUser: " + cloneUser);
        }
    }
    
    
    重新测试结果:
    User{name='小明', age=18, school=School{name='北京'}}
    ------------------------------------------------
    user1: User{name='小明', age=18, school=School{name='上海'}}
    cloneUser: User{name='小明', age=18, school=School{name='北京'}} // 没改变
    

5. RandomAccess标记接口:支持快速的随机访问

  1. 作用:是允许通用算法更改其行为,以便在应用于随机访问列表或者顺序访问列表时提供良好的性能。

  2. 此接口由list实现使用,以表明它们支持快速的随机访问。 实现了这个接口的集合是支持快速随机访问策略的。

  3. 通俗来说: 如果是实现了这个接口的 List,那么使用for循环的方式(随机访问)获取数据会优于用**迭代器(顺序访问)**获取数据。

  4. 什么是随机访问

    for(int i = 0; i < 10; i++)
        list.get(i);
    
  5. 什么是顺序访问:

    while (iterator.hasNext()){
            Object next = iterator.next();
    }
    
  6. 验证代码

    public class Test3 {
        public static void main(String[] args) {
            // 实现了RandomAccess接口----for方法更加快
            ArrayList<Integer> list = new ArrayList<>();
            for (int i = 0; i < 100000; i++) {
                list.add(i);
            }
            // for方法 随机访问
            t1(list);
            // 迭代器 顺序访问
            t2(list);
        }
    
        private static void t1(ArrayList<Integer> list) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < list.size(); i++) {
                Object object = list.get(i);
            }
            long end = System.currentTimeMillis();
            System.out.println("for随机访问消耗的时间: " + (end - start));
        }
    
        private static void t2(ArrayList<Integer> list) {
            long start = System.currentTimeMillis();
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()){
                Object object = iterator.next();
            }
            long end = System.currentTimeMillis();
            System.out.println("iterator顺序访问消耗的时间: " + (end - start));
        }
    }
    
    运行结果:
    for随机访问消耗的时间: 6
    iterator顺序访问消耗的时间: 7
    
  7. 我们使用LinkedList来测试一下,LinkedList并没实现RandomAccess接口。

    public class Test3 {
        public static void main(String[] args) {
            // LinkedList 没有实现了RandomAccess接口----iterator方法更加快
            LinkedList<Integer> list = new LinkedList<>();
            for (int i = 0; i < 100000; i++) {
                list.add(i);
            }
            // for方法 随机访问
            t1(list);
            // 迭代器 顺序访问
            t2(list);
        }
    
        private static void t1(LinkedList<Integer> list) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < list.size(); i++) {
                Object object = list.get(i);
            }
            long end = System.currentTimeMillis();
            System.out.println("for随机访问消耗的时间: " + (end - start));
        }
    
        private static void t2(LinkedList<Integer> list) {
            long start = System.currentTimeMillis();
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()){
                Object object = iterator.next();
            }
            long end = System.currentTimeMillis();
            System.out.println("iterator顺序访问消耗的时间: " + (end - start));
        }
    }
    
    运行结果:
    for随机访问消耗的时间: 6542
    iterator顺序访问消耗的时间: 7
    

    小结:

    1. 如果实现了RandomAccess接口,则for方法遍历比iterator迭代器方法遍历好,如:Arraylist
    2. 如果没实现,如上面例子LinkedList,则迭代器方法远优于for方法遍历。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值