• Java进阶


    如果你想复制一个简单变量。很简单:

    int x = 7;  
    int y = x;

    不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

    但是如果你复制的是一个对象,情况就有些复杂了。

    当我说我是一个beginner的时候,我会这样写:

    class Car{
    
        private String color;
        private String name;
    
        Car(String color, String name) {
            this.color = color;
            this.name = name;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Car [color=" + color + ", name=" + name + "]";
        }
    }
    
    public class Test{
    public static void main(String[] args) throws Exception {
    Car car
    = new Car("白色", "奔驰"); Car cloneCar = car; System.out.println("car = " + car); System.out.println("cloneCar = " + cloneCar); } }

    结果:

    car = Car [color=白色, name=奔驰]

    cloneCar = Car [color=白色, name=奔驰]

    这里我们自定义了一个Car类,该类有color和name字段。

    我们新建了一个Car实例,然后将该值赋值给cloneCar实例。(Car cloneCar = car;)

    再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此!

    难道真的是这样吗?我们试着改变cloneCar实例的name字段,再打印结果看看:

    cloneCar.setName("大众");
    System.out.println("car = " + car);
    System.out.println("cloneCar = " + cloneCar);

    结果:

    car = Car [color=白色, name=大众]

    cloneCar = Car [color=白色, name=大众]

    为什么改变cloneCar属性,car属性也发生了变化呢?

    原因出在(cloneCar = car) 这一句。该语句的作用是将car的引用赋值给cloneCar,这样,car和cloneCar指向内存堆中同一个对象。

    那么才能复制一个对象呢?

    这就要用到我们的Object类了。它有11个方法,有两个protected的方法,其中一个为clone方法。

    在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码。你会发现里面有一个访问限定符为protected的方法clone():

    /* 
       Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
       The general intent is that, for any object x, the expression:
       1) x.clone() != x will be true
          保证克隆对象将有单独的内存地址分配。
       2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
          原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
       3) x.clone().equals(x) will be true, this is not an absolute requirement. 
         原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
    */
    protected native Object clone() throws CloneNotSupportedException;

    仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

    因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

    要想对一个对象进行复制,就需要对clone方法覆盖。

    一、为什么要克隆?

    为什么需要克隆对象?直接new一个对象不行吗?

    答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。

    那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone 是一个 native 方法,就是快啊,在底层实现的。

    常见的 Object a = new Object();Object b = a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。

    而通过clone方法赋值的对象跟原来的对象时同时独立存在的。

    二、如何实现克隆

    Java中有两种不同的克隆方法:浅克隆(ShallowClone)和深克隆(DeepClone)

    (1)浅克隆:实现Cloneable接口并重写Object类中的clone()方法;

    (2)深克隆:实现Serializable接口,通过对象的序列化和反序列化实现克隆;

    Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

    - 注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

    三、浅克隆

    Car类:

    class Car implements Cloneable {
        private String color;
        private String name;
    
        Car() {
            super();
        }
    
        Car(String color, String name) {
            this.color = color;
            this.name = name;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Car [color=" + color + ", name=" + name + "]";
        }
    }
    Car.java

    Persion类:实现Cloneable接口,需要重写clone方法。

    class Persion implements Cloneable {
    
        private int age;
        private String name;
        private Car car;
    
        public Persion() {
            super();
        }
    
        public Persion(int age, String name, Car car) {
            super();
            this.age = age;
            this.name = name;
            this.car = car;
        }
    
        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;
        }
    
        public Car getCar() {
            return car;
        }
    
        public void setCar(Car car) {
            this.car = car;
        }
    
        @Override
        public String toString() {
            return "Persion [age=" + age + ", name=" + name + ", car=" + car + "]";
        }
    
        /**
         *     实现Cloneable接口,并重写clone方法
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Persion persion = null;
            try {
                /**
                 *     若要实现深克隆,此处就必须将对象中所有的复合数据类型统统单独复制拷贝一份, 但是实际开发中,无法确定对象中复合数据的种类和个数,
                 *     因此一般不采用此种方式实现深克隆
                 */
                persion = (Persion) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return persion;
        }
    }
    Persion.java

    测试类:

    public class Test {
    
        public static void main(String[] args) throws Exception {
    
            Car car = new Car("白色", "奔驰");
            Persion p1 = new Persion(24, "阿大", car);
    
            Persion p2 = (Persion) p1.clone();
            p2.setAge(17);
            p2.setName("小黄");
            p2.getCar().setName("大众");
    
            System.out.println("p1 == p2 ? " + (p1 == p2));
            System.out.println(p1);
            System.out.println(p2);
        }
    }

    结果:小黄将阿大的奔驰变成大众。

    修改基础数据结构数值不会影响其他对象,修改复合数据数值,全部都会受影响;

    四、深克隆

    Car类:必须实现Serializable接口

    class Car implements Serializable {
        private static final long serialVersionUID = -6593855835779505248L;
    
        private String color;
        private String name;
    
        Car() {
            super();
        }
    
        Car(String color, String name) {
            this.color = color;
            this.name = name;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Car [color=" + color + ", name=" + name + "]";
        }
    }
    Car.java

    Persion类:必须实现Serializable接口

    class Persion implements Serializable {
    
        private static final long serialVersionUID = -630014110607564384L;
    
        private int age;
        private String name;
        private Car car;
    
        public Persion() {
            super();
        }
    
        public Persion(int age, String name, Car car) {
            super();
            this.age = age;
            this.name = name;
            this.car = car;
        }
    
        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;
        }
    
        public Car getCar() {
            return car;
        }
    
        public void setCar(Car car) {
            this.car = car;
        }
    
        @Override
        public String toString() {
            return "Persion [age=" + age + ", name=" + name + ", car=" + car + "]";
        }
    
    }
    Persion.java

    CloneUtil - 克隆工具类:

    class CloneUtil {
    
        @SuppressWarnings("unchecked")
        public static <T extends Serializable> T clone(T object) throws Exception {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bout);
            oos.writeObject(object);
    
            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bin);
            // 此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
            // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
            return (T) ois.readObject();
        }
    }

    测试:

    public class Test{
    
        public static void main(String[] args) throws Exception {
    
            Car car = new Car("白色", "奔驰");
            Persion p1 = new Persion(24, "阿大", car);
    
            Persion p2 = CloneUtil.clone(p1);
            p2.setAge(17);
            p2.setName("小黄");
            p2.getCar().setName("大众");
    
            System.out.println("p1 == p2 ? " + (p1 == p2));
            System.out.println(p1);
            System.out.println(p2);
        }
    }

    结果:奔驰还是奔驰,大众还是大众。

    由输出结果来看,p2对象的car值修改不影响p1对象的car值,即使用序列化可以实现对象的深拷贝。

  • 相关阅读:
    《编程语言实现模式》读后感3
    编程语言实现模式读后感3
    图表联动
    毕业设计第三周第三天完成情况汇总
    毕业设计第三周第二天完成情况汇总
    毕业设计第三周第一天完成情况汇总
    毕业设计第三周整体规划
    毕业设计第二周第六天完成情况汇总
    毕业设计第二周第五天完成情况汇总
    毕业设计第二周第四天完成情况汇总
  • 原文地址:https://www.cnblogs.com/Dm920/p/12525749.html
Copyright © 2020-2023  润新知