1.在不恰当的时候使用了引用
场景:有两个List,存有元素的students,暂无元素的studentList,现需要将students中的元素在studentList中拷贝一份!
代码如下:
实体类:
/**定义一个实体类*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(String name) {
this.name = name;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
测试类:
public class Run {
/**
* 初始化列表
* @return
*/
private List<Student> createStudentList(){
List<Student> students = new ArrayList<>(3);
students.add(new Student("李明"));
students.add(new Student("刘宇"));
students.add(new Student("启华"));
return students;
}
}
在测试类中添加测试方法1:
@Test
public void test1(){
List<Student> students = createStudentList();
List<Student> studentList = new ArrayList<>(students.size());
System.out.println(students);
System.out.println("===================");
Student student = new Student();
for (Student s : students) {
student = s;
studentList.add(student);
}
System.out.println(studentList);
}
输出结果:
[Student{name='李明'}, Student{name='刘宇'}, Student{name='启华'}]
===================
[Student{name='李明'}, Student{name='刘宇'}, Student{name='启华'}]
符合预期,没有问题!
在测试类中添加测试方法2:
@Test
public void test2(){
List<Student> students = createStudentList();
List<Student> studentList = new ArrayList<>(students.size());
System.out.println(students);
System.out.println("===================");
Student student = new Student();
for (Student s : students) {
student.setName(s.getName());
studentList.add(student);
}
System.out.println(studentList);
}
输出结果:
[Student{name='李明'}, Student{name='刘宇'}, Student{name='启华'}]
===================
[Student{name='启华'}, Student{name='启华'}, Student{name='启华'}]
出问题了!
两个测试方法区别只在第9行代码,如果不看代码的执行结果,初时觉得测试方法2的写法没有问题.后来经过研究才发现问题所在.
分析一下测试方法的代码:
List<Student> students = createStudentList();
首先,,我们初始化了一个长度为3的ArrayList可以看做是名字叫做students的商店,将其中的3个元素看做名字叫”李明”,”刘宇”,”启华”三个人,他们在商店门前排队,手上分别拿着号牌0,1,2(即他们的对应下标);
List<Student> studentList = new ArrayList<>(students.size());
然后,,新开了一家商店studentList,也在外面发号牌,
Student student = new Student();
相当于又来了一个人,但是不知道具体是谁!暂时叫他x,
在测试方法1中,
for (Student s : students) {
student = s;
studentList.add(student);
}
x相当于一个中间人,”李明”,”刘宇”,”启华”三个人分别通过x拿到了studentList商店的号牌;
循环时:第一次x把号牌0交给”李明”,第二次把号牌1交给”刘宇”,第三次把号牌2交给”启华”,号牌发完后,在商店studentList排队的还是实实在在的那三个人,至于x去哪了,号牌发完他也就该回家了(垃圾回收)!
为进行验证,我们输出其地址:
[com.common.test.quartz.one.Student@2d363fb3, com.common.test.quartz.one.Student@7d6f77cc, com.common.test.quartz.one.Student@5aaa6d82]
===================
[com.common.test.quartz.one.Student@2d363fb3, com.common.test.quartz.one.Student@7d6f77cc, com.common.test.quartz.one.Student@5aaa6d82]
在测试方法2中,
for (Student s : students) {
student.setName(s.getName());
studentList.add(student);
}
循环时,第一次x把名字改成”李明”拿到号牌0,第二次x把名字改成”刘宇”拿到号牌1,第三次x把名字改成”启华”拿到号牌2,结果就是号牌发完,在商店studentList排队的就x一个人,这是后他的名字就是”启华”了,所以你找studentList的号牌为0,1,2的人其实找的都是x(名字是叫”启华”),但是跟students中的”启华”不是同一个人!
输出其地址:
[com.common.test.quartz.one.Student@2d363fb3, com.common.test.quartz.one.Student@7d6f77cc, com.common.test.quartz.one.Student@5aaa6d82]
===================
[com.common.test.quartz.one.Student@73a28541, com.common.test.quartz.one.Student@73a28541, com.common.test.quartz.one.Student@73a28541]
额外思考
此时修改测试方法2为
@Test
public void test2(){
List<Student> students = createStudentList();
List<Student> studentList = new ArrayList<>(students.size());
System.out.println(students);
System.out.println("===================");
for (Student s : students) {
Student student = new Student();
student.setName(s.getName());
studentList.add(student);
}
System.out.println(studentList);
}
这里我们把student的声明放在了循环体内部,然后执行结果为:
[Student{name='李明'}, Student{name='刘宇'}, Student{name='启华'}]
===================
[Student{name='李明'}, Student{name='刘宇'}, Student{name='启华'}]
执行结果与方法一相同,但是深究起来还是有区别的:
for (Student s : students) {
Student student = new Student();
student.setName(s.getName());
studentList.add(student);
}
这种写法相当于每次循环时都在路上随便拉个人,然后把他们名字分别改成”李明”,”刘宇”和”启华”,然后再塞给他们对应的号牌(这场景莫名有点像商家雇人过来排队的),最后排队的三个人虽然也叫这几个名字,但是跟原来那几个可都不是同一个人了.
输出其地址:
[com.common.test.quartz.one.Student@2d363fb3, com.common.test.quartz.one.Student@7d6f77cc, com.common.test.quartz.one.Student@5aaa6d82]
===================
[com.common.test.quartz.one.Student@73a28541, com.common.test.quartz.one.Student@6f75e721, com.common.test.quartz.one.Student@69222c14]
总结
方法一:在循环体外声明对象的引用,每次循环时将该引用指向新的目标对象,不易出错且有效复用了已有对象;
方法二:在循坏体外声明对象的引用,每次循环对该引用指向的对象进行操作,容易造成不可预测的错误;
方法三:不易造成错误,但是创建非必要的对象(多出来三个名字一样的人),浪费内存空间.
综上:方法一的写法甚佳!(但是如果使用stream形式(foreach
)进行遍历的话,只能使用第三种写法!)