集合中存的是引用,分析一道容易混淆的Java面试题
我们自定义的类是以引用的形式放入集合,如果使用不当,会引发非常隐蔽的错误。就拿我经常问到的一个面试题来说明这个知识点。
第一步,我们定义一个Car类型的类,其中只有一个int类型id属性。
第二步,创建一个Car类的实例,假设是c,设置它的id是1。
第三步,我们通过new关键字创建两个不同的ArrayList,分别是a1和a2,这里请注意我的描述,是创建两个不同的ArrayList,而不是一个,并把第二步里创建的c对象分别放入a1和a2。
第四步,我们在a1这个ArrayList里,拿出c对象,并把它的id设置成2,同时不对存放在a2里的c对象做任何的修改。
我的问题是,完成上述四步后,a2里存放的c对象的id是多少?1还是2?
我们通过如下的CopyDemo.java来分析这个问题:
1 import java.util.ArrayList;
2 //第一步,创建一个Car类,其中只有一个属性i
3 //通过构造函数,我们可以设置i的值,而且有针对i的get和set方法
4 class Car {
1 private int i;
2 public int getI() {return i;}
3 public void setI(int i) {this.i = i; }
4 public Car(int i) {this.i = i; }
5 }
6 public class CopyDemo {
7 public static void main(String[] args) {
8 //第二步,创建一个Car的实例,其中的id是1
9 Car c = new Car(1);
10 //第三步,创建两个不同的ArrayList
11 ArrayList<Car> a1 = new ArrayList<Car>();
12 ArrayList<Car> a2 = new ArrayList<Car>();
13 //通过add方法把c分别加入到a1和a2里
14 a1.add(c);
15 a2.add(c);
16 //第四步,修改a1中的c对象,但不修改a2中的c对象
17 a1.get(0).setI(2);
18 //最后通过打印查看a2里c对象的id
19 System.out.println(a2.get(0).getI());
20 }
21 }
根据第19行的输出,虽然我们并没对a2里存放的c对象做任何的操作,但它的值也被改成了2,根据我面试下来的结果,估计有一半的初级程序员(工作经验3年以下)会回答错。
原因是我们之前提到过的:集合里存放的是引用。下面我们来详细说明这点。
第一,当执行完Car c = new Car(1);操作后,Java虚拟机会在内存里开辟一块空间存放id是1的c对象,假设这块空间的首地址是1000,那么c其实是指向1000号空间的引用。
第二,我们其实是把c这个引用放入到两个不同的ArrayList里,大家可以通过下图来观察下效果。
从上图里我们能看到,a1和a2里第一个元素存放的其实都是c这个引用。通过这个引用,都能指向到存放在1000号内存里id是1的这个c对象。
当我们通过a1修改存放在其中的c对象时,其实是通过c这个引用直接改变了1000号里的id,由于a2里存放的引用也是指向1000号内存,所以虽然我们并没有改变过a2里的c对象,但a2里的值也跟着变了。
在实际项目里,可能会遇到类似的问题。比如我们把同一份变量放入两个不同的集合对象里,我们的本意是,在一个集合里给该变量做个备份,只在另外一个集合里修改。但根据上文的描述,即使我们只在其中一个集合里做修改,这个修改会影响到另外一个我们企图做备份的集合,这和我们想当然的结果不一样。
如果要正确地实现“一个集合做备份另一个集合做修改”的效果,我们就得通过clone方法来实现深拷贝了,来看DeepCopy.java这个例子。
1 import java.util.ArrayList;
2 //实现Cloneable接口,重写其中的clone方法
3 class CarForDeepCopy implements Cloneable {
4 private int i;
5 public int getI() {return i;}
6 public void setI(int i) {this.i = i;}
7 //构造函数
8 public CarForDeepCopy(int i)
9 {this.i = i;}
10 //调用父类的clone完成对象的拷贝
11 public Object clone() throws CloneNotSupportedException
12 { return super.clone(); }
13 }
14 public class DeepCopy {
15 public static void main(String[] args) {
16 CarForDeepCopy c1 = new CarForDeepCopy(1);
17 //通过clone方法把c1做个备份
18 CarForDeepCopy c2 = null;
19 try {
20 c2 = (CarForDeepCopy)c1.clone();
21 } catch (CloneNotSupportedException e) {
22 e.printStackTrace();
23 }
24 ArrayList<CarForDeepCopy> a1 = new ArrayList<CarForDeepCopy>();
25 ArrayList<CarForDeepCopy> a2 = new ArrayList<CarForDeepCopy>();
26 a1.add(c1);
27 a2.add(c2);
28 //修改a1中的c对象的id为2
29 a1.get(0).setI(2);
30 //输出依然是1
31 System.out.println(a2.get(0).getI());
32 }
33 }
为了实现clone,我们自定义的类必须要像第3行那样实现Cloneable接口;同时像第11行那样重写clone方法。其中我们可以像第12行那样,通过super调用父类的clone方法来完成内容的拷贝。
在第20行里,我们通过调用c1对象的clone方法在内存里创建另外一个CarForDeepCopy对象,随后当我们通过第26和27行把c1和c2放入到两个ArrayList后,在内存里存储的结构如下图所示。
从中我们能看到,c1被clone后,系统会开辟一块新的空间用以存放和c1相同的内容,并用c2指向这块内存。
这样a1和a2两个ArrayList就通过c1和c2这两个不同的引用指向了两块不同的内存空间,所以a1的修改不会影响到a2对象。
集合中存的是引用,分析一道容易混淆的Java面试题的更多相关文章
- 【转】HashMap集合中key只能为引用数据类型,不能为基本类型
在HashMap中,为什么不能使用基本数据类型作为key? 其实和HashMap底层的存储原理有关,HashMap存储数据的特点是:无序.无索引.不能存储重复元素. 存储元素采用的是hash表存储数据 ...
- 一道非常棘手的 Java 面试题:i++ 是线程安全的吗
转载自 一道非常棘手的 Java 面试题:i++ 是线程安全的吗 i++ 是线程安全的吗? 相信很多中高级的 Java 面试者都遇到过这个问题,很多对这个不是很清楚的肯定是一脸蒙逼.内心肯定还在质疑 ...
- java集合中的HashMap源码分析
1.hashMap中的成员分析 transient Node<K,V>[] table; //为hash桶的数量 /** * The number of key-value mapping ...
- 一道月薪3W的java面试题 (小明和小强都是张老师的学生,张老师的生日是某月某日,2人都不知道张老师的生日)
小明和小强都是张老师的学生,张老师的生日是M月N日,2人都知道张老师的生日 是下列10组中的一天,张老师把M值告诉了小明,把N值告诉了小强,张老师问他们知道他的生日是那一天吗? 3月4日 3月5日 3 ...
- 一道简单到爆 Java面试题,居然挂了一票人
很多时候bug往往都是出在,我们觉得非常简单,不起眼的基础知识上 年前公司最后一波招人,为年后项目做技术储备,主要招聘对象初中级Java开发,要求也并没有多苛刻,唯一一点基础稍好,快速上手做项目就行. ...
- Java EE中的容器和注入分析,历史与未来
Java EE中的容器和注入分析,历史与未来 java中的容器 java中的注入 容器和注入的历史和展望 一.java中的容器 java EE中的注入,使我们定义的对象能够获取对资源和其他依赖项的引用 ...
- java代码实现将集合中的重复元素去掉
package com.loaderman.test; import java.util.ArrayList; import java.util.LinkedHashSet; import java. ...
- Java去除ArrayList集合中重复字符串的案例
ArrayList去除集合中的字符串重复值 分析: A:创建集合对象 B:添加多个字符串元素 C:创建新集合 D:遍历旧集合,获取得到每一个元素 E:拿着个元素到新集合去找,看有没有 有:不进去 没有 ...
- 一个关于集合的问题,为什么添加进List集合中的元素被莫名其妙的改变了
以前自己理解的不够深刻,特此记录一下提醒自己,如果正好也帮到了你,我会很开心.相信只有自己正好遇到这个问题,才觉得哦,原来这样.自己小白,大神莫喷 为什么添加进List集合中的元素被莫名其妙的改变了? ...
随机推荐
- (MonoGame从入门到放弃-2) 初识MonoGame
上一节记录了大致的搭建MonoGame的环境,默认大家都是都是使用过Visual Studio的,没使用过的话,可以去https://www.visualstudio.com/下载一个试试,社区版免费 ...
- 在Vue2.0中集成UEditor 富文本编辑器
在vue的'项目中遇到了需要使用富文本编辑器的需求,在github上看了很多vue封装的editor插件,很多对图片上传和视频上传的支持并不是很好,最终还是决定使用UEditor. 这类的文章网上有很 ...
- Git版本回退和撤销修改的区别
在阅读廖雪峰git教程时,对版本回退和暂存区撤销修改没太看懂,所以自己测试了一下. 版本回退: git reset --hard HEAD 这个命令用于版本回退,就是将已提交的版本覆盖本地工作区的内容 ...
- hdu1006 Tick and Tick
原题链接 Tick and Tick 题意 计算时针.分针.秒针24小时之内三个指针之间相差大于等于n度一天内所占百分比. 思路 每隔12小时时针.分针.秒针全部指向0,那么只需要计算12小时内的百分 ...
- HDU - 1584 IDA*
思路:裸的IDA*,估计当前状态至少需要多少距离才能达到目标状态,剪枝即可.每一墩牌只需记录其最上面和最下面的牌型即可完成移动. AC代码 #include <cstdio> #inclu ...
- CodeForces-749B
给定3个坐标,求可能构成平行四边形的第四个点,枚举两个点,根据这两个点的横纵坐标差,来得到第四个点的坐标,注意生成的坐标需要判重. AC代码: #include<cstdio> #incl ...
- SpringBoot SpringSession redis 共享 SESSION
号称无缝整合httpsession 共享, 但注意如果存在第三方框架,例如SESSION并发控制,这个是需要自己重写session名单的. 关于redis session 共享 的session并发控 ...
- Appium疑难杂症
坑之初体验 在Appium的初体验中,遇到了一些坑坑洼洼.将他们记录下来,以后方便查阅. 1. session大于60秒没接收到命令自动关闭 通过Appium-Python-Client连接到appi ...
- 笔记︱范数正则化L0、L1、L2-岭回归&Lasso回归(稀疏与特征工程)
机器学习中的范数规则化之(一)L0.L1与L2范数 博客的学习笔记,对一些要点进行摘录.规则化也有其他名称,比如统计学术中比较多的叫做增加惩罚项:还有现在比较多的正则化. -------------- ...
- R语言︱SNA-社会关系网络—igraph包(社群划分、画图)(三)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 社群划分跟聚类差不多,参照<R语言与网站 ...