Java对象克隆详解

154 篇文章 5 订阅
150 篇文章 6 订阅

在讲对象克隆之前,先要说明几个概念

  • 全局变量和局部变量
  • 值传递和引用传递
  • 序列化和反序列化

一、全局变量和局部变量

1.局部变量

  • 定义在类的方法内或者代码块内的变量
  • 局部变量只在定义它的方法体内或者代码块内有效
  • 局部变量在使用前,必须先初始化

2.全局变量(成员变量)

  • 一个类中既不在方法体内,也不在程序块内定义的变量
  • 类中所有的方法和代码块都可以访问成员变量
  • 对于没有初始化的成员变量,系统会指定默认的值
    在这里插入图片描述

3.注意

  • 局部变量的作用域范围从定义的位置开始到其所在语句块结束。如果局部变量的名字与全局变量的名字相同,则在局部变量的作用范围内全局变量被隐藏,即这个全局变量在同名局部变量所在方法内暂时失效。
  • 如果在局部变量的作用域范围内访问该成员变量,则必须使用关键字 this 来引用成员变量
  • 局部变量在使用以前必须显式初始化或赋值,局部变量没有默认值。
  • 声明局部变量时,数据类型前除 final 外不允许有其他关键字,也就是说,局部变量不可用由private,protected,public等关键字修饰

二、值传递和引用传递

(一)定义

1.值传递

在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。

2.引用传递

引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。

(二)java中数据类型

1.基本类型:编程语言中内置的最小粒度的数据类型。它包括四大类八种类型:

  • 4 种整数类型:byte、short、int、long
  • 2 种浮点数类型:float、double
  • 1 种字符类型:char
  • 1 种布尔类型:boolean

2.引用类型:引用也叫句柄,引用类型,是编程语言中定义的在句柄中存放着实际内容所在地址的地址值的一种数据形式。它主要包括:

  • 接口
  • 数组

(三)应用场景

1.值传递

【代码】

值传递传递的是真实内容的一个副本,对副本的操作不影响原内容,也就是形参怎么变化,不会影响实参对应的内容。

public class Test
{
     public static void changeStr(String str)
     {
         str = "welcome";
     }
     public static void main(String[] args)
     {
         String str = "1234";
         changeStr(str);
         System.out.println(str);
     }
}

//运行结果:
1234

2.引用传递

【代码】

引用也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向相同内存地址,对形参的操作会影响的真实内容。

public class Demo {
    public static void PersonCrossTest(Person person){
        System.out.println("传入的person的name:"+person.getName());
        person.setName("二哈");
        
        System.out.println("方法内重新赋值后的name:"+person.getName());
    }
    //测试
    public static void main(String[] args) {
        Person p=new Person();
        p.setName("藏獒");
        p.setAge(3);
        PersonCrossTest(p);
        System.out.println("方法执行后的name:"+p.getName());
    }
}

// 运行结果
传入的person的name:藏獒
方法内重新赋值后的name:二哈
方法执行后的name:二哈

3.分歧

【代码】

在方法中多了person = new Person(); 这条语句,结果就发生了变化

public class Demo {
    public static void PersonCrossTest(Person person){
        System.out.println("传入的person的name:"+person.getName());
        person = new Person();
        person.setName("二哈");
        System.out.println("方法内重新赋值后的name:"+person.getName());
    }
    //测试
    public static void main(String[] args) {
        Person p=new Person();
        p.setName("藏獒");
        p.setAge(3);
        PersonCrossTest(p);
        System.out.println("方法执行后的name:"+p.getName());
    }
}

//运行结果
传入的person的name:藏獒
方法内重新赋值后的name:二哈
方法执行后的name:藏獒

可以发现,person = new Person(); 这条语句似的形参的地址再次发生了变化,分歧在于,

  • 一种思想认为:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变,而在方法中复制这种操作对实参和形参产生不同的影响,所以这并不是引用传递。(String类型就是典型代表)
  • 另一种思想认为:实参传值给形参后,形参自己改变了地址,这就和引用传递无关了。所以这种传入引用类型的方式均是引用传递。

4.补充

String是不可变类,他的赋值语句,是等同于new 创建新对象的。所以String类型是值传递


三、序列化

(一)基本概念

  • 在 Java 中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用此对象。但是,我们创建出来的这些对象都存在于 JVM 中的堆(stack)内存中,只有 JVM 处于运行状态的时候,这些对象才可能存在。一旦 JVM 停止,这些对象也就随之消失;但是在真实的应用场景中,我们需要将这些对象持久化下来,并且在需要的时候将对象重新读取出来,Java 的序列化可以帮助我们实现该功能。

  • 对象序列化机制(object serialization)是 java 语言内建的一种对象持久化方式,通过对象序列化,可以将对象的状态信息保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式转换成对象,对象的序列化可以很容易的在 JVM 中的活动对象和字节数组(流)之间进行转换。

  • Java 类通过实现 java.io.Serialization 接口来启用序列化功能,未实现此接口的类将无法将其任何状态或者信息进行序列化或者反序列化。可序列化类的所有子类型都是可以序列化的。序列化接口没有方法或者字段,仅用于标识可序列化的语义。在 JAVA 中,对象的序列化和反序列化被广泛的应用到 RMI(远程方法调用)及网络传输中

  • 静态数据不能被序列化,因为静态数据不在堆内存中,而是在静态方法区中

  • 对于不需要序列化的属性, java 的 transient 关键字为我们提供了便利,你只需要实现 Serilizable 接口,将不需要序列化的属性前添加关键字 transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中

总结:

  • 在序列化的时候,被 transient 或者 static 修饰的属性,不可以序列化
  • 一个类可以被序列化,那么它的子类也可以被序列化
  • 序列化可以实现深复制,而 Object 中的 clone 实现的就只是浅复制

四、对象克隆(对象拷贝)

(一)定义

Java 中的对象拷贝 (Object Copy) 指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。举例说明:比如,对象 A 和对象 B 都属于类 S,具有属性 a 和 b。那么对对象 A 进行拷贝操作赋值给对象 B 就是:B.a=A.a; B.b=A.b;

在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部数据。

Student stu1 = new Student();
Student stu2 = stu1;

以上的代码,改变stu1或者stu2中任意一个对象的属性,另一个对象的属性也会随之改变。

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

Java 中的对象拷贝主要分为:浅拷贝 (Shallow Copy)、深拷贝 (Deep Copy)

(二)浅拷贝

一般步骤是(浅复制):

  • 被复制的类需要实现 Clonenable 接口(不实现的话在调用 clone 方法会抛出 CloneNotSupportedException 异常) 该接口为标记接口 (不含任何方法)

  • 覆盖 clone () 方法,。方法中调用 super.clone () 方法得到需要的复制对象,注意该方法需要处理异常。

【代码】

class Student implements Cloneable{
	private int number;
 
	public int getNumber() {
		return number;
	}
 
	public void setNumber(int number) {
		this.number = number;
	}
	
	@Override
	protected Object clone() {
		Student stu = null;
		try{
			stu = (Student)super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return stu;
	}
}

Student类实现Cloneable接口,并重写clone方法(重写只是简单调用父类的clone方法即可),对于类中只有基本数据类型的对象,浅复制即可解决问题。

但是对于那些类成员变量是对象的类,浅复制的方式就不行了,因为浅复制只是复制了对象的成员变量的引用,对于基本数据类型是创建新的值,但是对于引用数据类型的对象,这只是复制了引用,两者指向的还是同一个对象

(三)深拷贝

深拷贝的方式有多个

  • 第一种:与通过重写 clone 方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现 Cloneable 接口并重写 clone 方法,最后在最顶层的类的重写的 clone 方法中调用所有的 clone 方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝 = 深拷贝。

  • 第二种:结合序列化来解决这个问题,先把对象序列化,然后再反序列化成对象,该对象保证每个引用都是崭新的。这个就形成了多个引用,原引用和反序列化之后的引用不在相同,具体实现:

public class Student implements Cloneable, Serializable {

	// 属性

	@Override
    protected Object clone() {
        Student stu = null;
        try {
            // 将对象写成byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 从流中读出byte array
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            stu = (Student) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stu;
    }

}

Student类需要实现Cloneable、Serializable两个接口,而且student中的成员变量对象都要实现serializable接口


【Java 面试那点事】

这里致力于分享 Java 面试路上的各种知识,无论是技术还是经验,你需要的这里都有!

这里可以让你【快速了解 Java 相关知识】,并且【短时间在面试方面有跨越式提升】

面试路上,你不孤单!
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值