java引用的理解

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)进行遍历的话,只能使用第三种写法!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值