🦊 Java
1、Java基础
1.1 StringBuffer和StringBuilder的区别
1.2 ConcurrentHashMap和TreeMap实现原理
1.3 ArrayList和LinkedList实现原理
1.4 HashSet和TreeSet实现原理
1.5 深拷贝与浅拷贝
1.6 抽象类与接口
2、Java多线程
2.1 并发编程的三大特性
2.2 指令重排
2.3 Volatile原理
2.4 CAS原理
2.5 Java的4种引用级别
2.6 Java中的锁
2.7 Synchronized实现原理
2.8 线程池实现原理
2.9 AQS
2.10 创建线程的方式
2.11 ThreadLocal原理
3、JVM
3.1 判断对象是否存活的方法
3.2 JVM内存结构
3.3 常见的垃圾收集算法有哪些
3.4 指针碰撞和空闲列表
3.5 常见的垃圾收集器有哪些
3.6 内存溢出与内存泄漏的区别
3.7 常用的JVM启动参数有哪些
3.8 反射机制
4、NIO
4.1 概述
5、Spring
5.1 Spring IOC
5.2 Spring AOP
6、SpringBoot
6.1 SpringBoot、SpringCloud的联系与区别
-
+
游客
注册
登录
深拷贝与浅拷贝
## 1 数据类型 ### 1.1 分类 Java 中的数据类型可以分为两大类,分别是**基本类型**和**引用类型**。 #### 1.1.1 基本类型 Java 中基本类型一共是**四类八种**,具体如下: 1. **整型**:`byte`、`short`、`int`、`long`。 2. **浮点类型**:`float`、`double`。 3. **字符型**:`char`。 4. **逻辑型**:`boolean`。 #### 1.1.2 引用类型 除了上面四类八种基本类型外,其他的都是**引用类型**,**包括数组**。 ### 1.2 区别 1. **基本类型的变量保存原始值**,即他**代表的值就是数值本身**;而**引用类型的变量保存引用值**,**引用值指向内存空间的地址**,**代表了某个对象的引用**,而**不是对象本身**。 2. **基本类型在声明时系统就给他分配了内存空间**,**引用类型在声明时只给变量分配了引用空间**,而**没有分配数据空间**。 3. 具体示例如下: 1. 声明一个基本数据类型 `int1` 并赋值: ```java int int1 = 100; ``` ![](/media/202108/2021-08-08_1652590.1911524342504446.png) 2. 声明一个基本数据类型 `int2`,并赋值为 `int1`,对于**基本数据类型**来说,**赋值**(=号)**就相当于拷贝了一份值**,把 `int1` 的值 100,拷贝给 `int2`。: ```java int int2 = int1; ``` ![](/media/202108/2021-08-08_1658160.501736227391462.png) 3. 将 `int1` 的值修改为 500: ```java int1 = 500; ``` ![](/media/202108/2021-08-08_1700190.5325825164433003.png) 4. 此时,分别打印 `int1`、`int2` 的值,会输出为 500、100。 5. 然后,声明一个数组 `arr1` 并赋值,**当执行到 `new` 这个关键字时**,**会在堆内存分配内存空间**,**并把该内存空间的地址赋值给 `arr1`**: ```java int[] arr1 = new int[]{1, 2, 3, 4, 5}; ``` ![](/media/202108/2021-08-08_1703590.6470063288641562.png) 6. 定义一个数组 `arr2`,并赋值为 `arr1`,因为 `arr2`**初始化没有 `new` 关键字**,所以**并不会在堆内存里开辟一块空间**,而是**把 `arr1` 里存的堆内存地址直接赋值给了 `arr2`**,**对于引用类型来说**,**赋值**(=号)**就相当于拷贝了一份内存地址**,也就是说 `arr1`**和 `arr2` 现在指向了同一块堆内存**: ```java int[] arr2 = arr1; ``` ![](/media/202108/2021-08-08_1708240.3871234176341938.png) 7. 然后将数组 `arr1` 下标位置为 3 的值修改为 8,虽然只是修改了 `arr1` 数组下标位置为 3 的值,但由于数组 `arr1` 和数组 `arr2` 指向同一块堆内存,因此当打印 `arr1[3]` 和 `arr2[3]` 的值时,二者都为 8: ```java arr1[3] = 8; ``` ![](/media/202108/2021-08-08_1712040.9843170000779748.png) 8. 然后再看对象的初始化,定义一个类型为 `Person` 类的对象 `per1` 并将其实例化,其中 `Person` 类有两个属性,分别为 `String` 类型的 `name`、`int` 类型的 `age`,该对象实例化时,因为有 `new`,所以会在堆内存里开辟了一块内存空间: ```java public class Person { private int age; private String name; public Person(final int age, final String name) { this.age = age; this.name = name; } public int getAge() { return this.age; } public void setAge(final int age) { this.age = age; } public String getName() { return this.name; } public void setName(final String name) { this.name = name; } } ``` ```java Person per1 = new Person("张三", 21); ``` ![](/media/202108/2021-08-08_1721420.74289315441882.png) 9. 然后定义一个 `Person` 类型的对象 `per2`,并赋值为 `per1`,因为没有 `new` 关键字,所以 `per2` 不会在堆内存中开辟空间,和数组一样,也是把 `per1` 的内存地址直接赋值给了 `per2`: ```java Person per2 = per1; ``` ![](/media/202108/2021-08-08_1724160.34248708576343656.png) 10. 然后将 `per1` 的 `name` 修改为 `李四`,`age` 修改为 35,**给引用类型赋值是相当于引用重新指向一块堆内存**,**基本类型赋值是直接修改值**: ```java per1.setName("李四"); per1.setAge(35); ``` ![](/media/202108/2021-08-08_1728390.003278722558925251.png) 11. 此时,不管打印 `per1` 还是 `per2` 的 `name`、`age`,打印出来的结果都是 `李四`、35。 ## 2 对象的拷贝分类 对象的拷贝主要有三种方式,分别为**直接赋值**、**浅拷贝**、**深拷贝**。 ### 2.1 直接赋值 1. 用等号直接赋值是我们平时最常用的一种方式,它的特点就是**直接引用等号右边的对象**。 2. 如果这些对象都是**基本类型**,当然**没什么问题**,但是如果都是**引用类型**,那么**对一个对象的更改就会影响到另一个对象**。 3. 具体的示例如下可参考上面的[1.2 区别](#1-2-区别)。 ### 2.2 浅拷贝 1. 浅拷贝是**按位拷贝对象**,他**会创建一个新对象**,这个对象**有着原始对象属性值的一份精确拷贝**: 1. 如果**属性是基本类型**,**拷贝的就是基本类型的值**。 2. 如果**属性时引用类型**,**拷贝的就是内存地址**,此时**如果其中一个对象改变了这个地址**,**就会影响到另一个对象**。 2. Java 中使用浅拷贝需要类**实现 `Cloneable` 接口**,然后**重写 `clone()` 方法**。 3. 具体的示例如下: 1. 创建两个类,分别为 `Person` 和 `Friend`,其中 `Person` 实现了 `Cloneable` 接口,并重写了 `clone()` 方法(直接调用父类的 `clone()` 方法): ```java @Data @AllArgsConstructor public class Friend { private String name; } ``` ```java @Data @AllArgsConstructor public class Person implements Cloneable{ private String name; private int age; private Friend friend; @Override protected Person clone() throws CloneNotSupportedException { return (Person)super.clone(); } } ``` 2. 然后进行测试: ```java public class ShallowCopyTest { public static void main(String[] args) throws CloneNotSupportedException { Person person1 = new Person("张三", 20, new Friend("老王")); Person person2 = person1.clone(); System.out.println("person1: " + person1); System.out.println("person2: " + person2 + "\n"); person1.setName("张四"); person1.setAge(25); person1.getFriend().setName("小王"); System.out.println("person1: " + person1); System.out.println("person2: " + person2); } } ``` 输出的结果为: ```txt person1: Person(name=张三, age=20, friend=Friend(name=老王)) person2: Person(name=张三, age=20, friend=Friend(name=老王)) person1: Person(name=张四, age=25, friend=Friend(name=小王)) person2: Person(name=张三, age=20, friend=Friend(name=小王)) ``` 可以看到 `person1` 的值修改了之后,`person2` 中的 `name`、`age` 属性的值没有变,`friend` 属性的值变了,这是因为: 1. **String 类型为不可变对象**,**当需要修改不可变对象的值时**,**需要在内存中生成一个新的对象来存放新的值**,**然后将原来的引用指向新的地址**。 2. 我们**修改了 `person1` 对象的 `name` 属性值**,**`person1` 对象的 `name` 字段指向了内存中新的 `String` 对象**,但是我们**没有修改 `person2` 对象的 `name` 属性值**,所以 `person2`**对象的 `name` 字段还是指向内存中原来的 `String` 地址**。 3. 同时,我们**只修改了 `person1` 对象的 `age` 值**,而**没有修改 `person2` 对象的 `age` 值**,因此 `person2`**对象的 `age` 值不会发生变化**。 4. 我们**修改了 `friend` 对象的 `name` 值**,因此**对象的 `name` 值会指向新的 `String` 对象**,但是 `friend`**对象的地址并没有发生改变**,而 `person1`**和 `person2` 指向的是同一个 `friend` 对象的地址**,因此 `person1`**和 `person2` 对象的 `firend` 对象的 `name` 值都会发生变化**。 ![](/media/202108/2021-08-09_113523.png) ![](/media/202108/2021-08-09_113532.png) ### 2.3 深拷贝 1. 深拷贝是指**无论是值类型还是引用类型都会完完全全的拷贝一份**,**在内存中生成一个新的对象**,即**把要复制的对象所引用的对象都复制一遍**,**拷贝对象和被拷贝对象没有任何关系**,**互不影响**。 2. 但是**深拷贝相比于浅拷贝速度较慢并且花销较大**。 3. 深拷贝的实现方式有两种: 1. 一种是在被引用的对象所在的类中**实现 `Cloneable` 接口**,然后**重写 `clone()` 方法**,接着**在引用的类的 `clone()` 方法中调用被引用类的 `clone()` 方法,把被引用的类也复制一份**。 2. **将会被复制到的引用对象实现 `Serializable` 接口**,**通过序列化的方式实现深拷贝**,因为**对象被序列化成流后**,**写在流里的是对象的一个拷贝**,而**原对象仍然存在虚拟机里面**,探后**通过反序列化就可以得到一个完全相同的拷贝**。 4. 具体的示例如下: 1. 将 `Person` 类和 `Friend` 类都实现 `Cloneable` 接口,然后重写 `clone()` 方法: ```java @Data @AllArgsConstructor public class Friend implements Cloneable{ private String name; @Override protected Friend clone() throws CloneNotSupportedException { return (Friend)super.clone(); } } ``` ``` @Data @AllArgsConstructor public class Person implements Cloneable{ private String name; private int age; private Friend friend; @Override protected Person clone() throws CloneNotSupportedException { Person person = (Person)super.clone(); person.friend = friend.clone(); return person; } } ``` 2. 然后进行测试: ```java public class DeepCopyTest { public static void main(String[] args) throws CloneNotSupportedException { Person person1 = new Person("张三", 20, new Friend("老王")); Person person2 = person1.clone(); System.out.println("person1: " + person1); System.out.println("person2: " + person2 + "\n"); person1.setName("张四"); person1.setAge(25); person1.getFriend().setName("小王"); System.out.println("person1: " + person1); System.out.println("person2: " + person2); } } ``` 输出的结果为: ``` person1: Person(name=张三, age=20, friend=Friend(name=老王)) person2: Person(name=张三, age=20, friend=Friend(name=老王)) person1: Person(name=张四, age=25, friend=Friend(name=小王)) person2: Person(name=张三, age=20, friend=Friend(name=老王)) ``` 可以看到,这次真正独立了起来: ![](/media/202108/2021-08-09_120849.png) ![](/media/202108/2021-08-09_121011.png) 3. 需要注意的是,**如果 `Friend` 类本身也存在引用类型**,**则需要在 `Friend` 类中的 `clone()` 也去调用其引用类型的 `clone()` 方法**,**就像 `Person` 类中的那样**。 4. 因此**对于存在多层依赖关系的对象**,**实现 `Cloneable` 接口重写 `clone()` 方法就显得有些笨拙了**,所以**可以采用序列化实现深拷贝**,具体示例如下: 1. 修改 `Person` 和 `Friend`,实现 `Serializable` 接口: ```java @Data @AllArgsConstructor public class Person implements Serializable { private String name; private int age; private Friend friend; public Person deepClone() throws IOException, ClassNotFoundException { // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (Person)ois.readObject(); } } ``` ```java @Data @AllArgsConstructor public class Friend implements Serializable { private String name; } ``` 2. 然后进行测试: ```java public class DeepCopyTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Person person1 = new Person("张三", 20, new Friend("老王")); Person person2 = person1.deepClone(); System.out.println("person1: " + person1); System.out.println("person2: " + person2 + "\n"); person1.setName("张四"); person1.setAge(25); person1.getFriend().setName("小王"); System.out.println("person1: " + person1); System.out.println("person2: " + person2); } } ``` 输出的结果为: ``` person1: Person(name=张三, age=20, friend=Friend(name=老王)) person2: Person(name=张三, age=20, friend=Friend(name=老王)) person1: Person(name=张四, age=25, friend=Friend(name=小王)) person2: Person(name=张三, age=20, friend=Friend(name=老王)) ``` 可以看到,这次也真正独立了起来。 ## 参考文献 1. [Java 基本数据类型和引用类型](https://zhuanlan.zhihu.com/p/28654272)。 2. [基本类型和引用类型的区别](https://zycode1561.github.io/2019/12/07/%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%8C%BA%E5%88%AB)。 3. [深拷贝和浅拷贝的区别和与原理](https://www.cnblogs.com/yuwenjing0727/p/13607651.html)。 4. [Java 轻松理解深拷贝与浅拷贝【Java 资料站】](https://mp.weixin.qq.com/s?src=11×tamp=1628410348&ver=3239&signature=CPCcima67JXcLvAbgnBaZwhsbR4BY5x2ytfNIdnII2IlhS*0nb15pbYN6*aqO9uMB-fuhlg58orlUC42Vy*XEzTDbgBoAgztfdeR-f3xj7dmSZJzRUc1OGQVakZMav0X&new=1)。 5. [浅拷贝](https://baike.baidu.com/item/%E6%B5%85%E6%8B%B7%E8%B4%9D/8648181)。
ricear
2022年1月1日 14:35
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码