for-each 与引用类型、参数传递组合考点

for-each 与引用类型、参数传递组合考点

  • for-each loop
    • for-each loop traverse array
    • for-each 循环的等价代码
    • 原始类型值的复制
  • 自定义类对象作为参数传递
    • 自定义 toString 方法
    • 普通 for 循环遍历对象数组
    • 对象的别名 alias name
    • for-each 与保存原始类型的数组
    • 对象数组与 for-each 循环
    • 等效代码
  • ArrayList 使用 for-each 的行为和 array 相同

for-each loop

for-each loop traverse array

当我们想要访问数组或者列表中的每一个元素,但是又不关心索引的时候,适合使用 for-each 循环。

int[] hs = {12345}; for(int element : hs)
{
    System.out.println(element);
}
1
2
3
4
5

for-each 循环的等价代码

对于原始类型的数组,利用 for-each loop 无法改变元素的值。

int[] hs = {12345}; // for-each loop 相当于下面的代码 // int element = hs[0]; // 把 hs[0] 的副本赋值给 element // // do something // element 的变化,不影响 hs[0] // elment = hs[1]; // // do something // elment = hs[2]; // // do something // elment = hs[3]; // // do something // elment = hs[4]; // // do something for(int element : hs)
{
    element = 0// 试图把所有元素都变成 0 } for(int element : hs)
{
    System.out.println(element);
} // 输出的内容还是数组原来的内容 
1
2
3
4
5

但是结果没有变成 0,这是因为,hs 是一个整数类型的数组,int 是原始类型,赋值是建立一个副本,然后再赋值给 element,相当于 element = hs[0], element = hs[1],但是因为是整数,所以 element 中保存的是 hs[0] 中的数值的副本,所以修改 element 不能修改索引为 0 的值,或者不能修改数组对应的值。

原始类型值的复制

需要注意的是,参数传递的过程就是把实际参数的值赋值给形式参数的过程。原始类型的复制是创建副本 copy 然后传递给形式参数;引用类型(指向对象的变量)里面保存的是地址,所以引用类型实际参数赋值给形式参数把对象的地址复制了一份传递给形式参数,这就是为什么说对象传递的是索引。

for-each 会依次把数组中的元素赋值给 element 循环变量,但是这个赋值是创建了副本的,element 的变化不影响数组中原来的值。

int a = 2; int b = a; // b 中保存的是 a 的副本,原始类型赋值会创建副本 // b 的变化不影响 a b = 10;
System.out.println(a); // a 的值仍然是 2 
2

这个结论,对于任意的原始类型的数组,都是成立的。

自定义类对象作为参数传递

class Student {
    private String name;
    public Student(String n)     {
        name = n;
    }
    // accessor     public void setName(String n) {name = n;}
    // mutator     public String getName() return name;}
}

数组中除了原始类型,还可以保存引用类型reference type,或者说可以保存对象。

Student[] guys = {new Student("lucy"), 
    new Student("lily"), 
    new Student("tom")};
for (Student stu : guys)
{
   System.out.println(stu); // 输出的是默认 toString 的结果 }

下面输出的是 jupyter notebook 环境中,对象 toString 方法的默认的实现,我们可以自定义 toSring 方法,自行定义对象的字符串表示形式。

REPL.$JShell$16C$Student@4af3eec1
REPL.$JShell$16C$Student@537aa7fe
REPL.$JShell$16C$Student@60221fa7
class Student {
    private String name;
    public Student(String n)     {
        name = n;
    }
    public void setName(String n) {name = n;}
    public String getName() return name;}
    public String toString() {return "[Student: " + name + "]";}
}

自定义 toString 方法

我们定义了自己的版本的 toString 然后我们在看打印的效果:

Student[] guys = {new Student("lucy"), new Student("lily"), new Student("tom")}; for (Student stu : guys)
{
   System.out.println(stu); // 输出的是默认 toString 的结果 }
[Student: lucy]
[Student: lily]
[Student: tom]

普通 for 循环遍历对象数组

我们发现,对于对象数组,我们仍然可以方便的使用 for-each 循环么,我们看下用普通 for 循环达到相同效果:

Student[] guys = {new Student("lucy"), new Student("lily"), new Student("tom")}; for (int i = 0; i < guys.length; i++)
{
   System.out.println(guys[i]); // 输出的是默认 toString 的结果 }
[Student: lucy]
[Student: lily]
[Student: tom]

如果是要访问方法就更麻烦。

Student[] guys = {new Student("lucy"), new Student("lily"), new Student("tom")}; for (int i = 0; i < guys.length; i++)
{
   System.out.println(guys[i].getName()); // 输出的是默认 toString 的结果
}

对象的别名 alias name

我们知道,对于对象来说,变量其实是指向对象在内存的地址,所以我们也可以把这些变量叫做对象的引用。
比如说:Student guy = new Student("zhangsan");,guy 其实就是对 Student 对象的引用,guy 引用的那个对象的名字是 zhangsan。如果我们执行这样代码:Student s = guy 那么我们知道,guy 中地址,创建一个副本,然后赋值给 s,所以引用类型其实传递的是对象在内存中的地址。

Student guy = new Student("zhangsan");
Student s = guy; // s is alias name of guy object // guy 和 s 中保存了对象在内存的地址,是一个对对象的引用 

如果我在 s 上调用方法,同时会修改 guy 指向的对象:

s.setName("laohuang");
System.out.println(guy.getName());
laohuang

我们虽然没有在 guy 上调用 name 属性的修改器,但是 s 和 guy 其实指向的是同一个对象,所以 s 上调用方法,和在 guy 上调用方法是等效的。

guy.setName("lisi");

System.out.println(s.getName());
lisi

for-each 与保存原始类型的数组

对于原始类型来说,我们无法通过 for-each 循环来修改数组中的元素:

int[] hs = {12345}; for(int element : hs)
{
    element = 0// 试图把所有元素都变成 0 } for(int element : hs)
{
    System.out.println(element);
}
1
2
3
4
5

对象数组与 for-each 循环

但是,对于对象数组来说,利用 for-each 循环,通过循环变量是可以修改数组中元素的属性的,举例如下:

Student[] guys = {new Student("lucy"), 
                  new Student("lily"), 
                  new Student("tom")}; for (Student stu : guys)
{
   stu.setName("lisi"); // stu 其实依次是每个元素的别名,所以可以修改属性 } for (Student stu : guys)
{
   System.out.println(stu); // 输出的是默认 toString 的结果 }
[Student: lisi]
[Student: lisi]
[Student: lisi]

等效代码

对于 for-each 循环来说,相当于下面的代码:

Student[] guys = {new Student("lucy"), new Student("lily"), new Student("tom")}; for (Student stu : guys)
{
   System.out.println(stu); // 输出的是默认 toString 的结果 }


Student stu = guys[0]; // stu 实际上 new Student("lucy") 的别名 alias name stu.setName("lisi");
stu = guys[1];
stu.setName("lisi");
stu = guys[2];
stu.setName("lisi"); for (Student stu : guys)
{
   System.out.println(stu); // 输出的是默认 toString 的结果 }
[Student: lucy]
[Student: lily]
[Student: tom]
[Student: lisi]
[Student: lisi]
[Student: lisi]

ArrayList 使用 for-each 的行为和 array 相同

对于对象来说,无论是 array 还是 ArrayList,利用 for-each 的循环变量,都是可以修改数组或者列表中的元素的。

ArrayList<Student> guys = new ArrayList<>();

guys.add(new Student("zhangsan"));
guys.add(new Student("lucy"));
guys.add(new Student("wangwu")); for (Student stu : guys) // stu 是引用类型,赋值传递的是引用,而非副本 {
   stu.setName("lisi"); // stu 其实依次是每个元素的别名,所以可以修改属性 } for (Student stu : guys)
{
   System.out.println(stu); // 输出的是默认 toString 的结果 }
[Student: lisi]
[Student: lisi]
[Student: lisi]

可以看到,利用循环变量 stu 依次调用方法,可以修改列表对象的属性;这是因为,对于对象来说,引用的赋值创建的地址的副本,stu 依次是列表中每个元素别名,从而使得这个时候可以修改元素。

for-each 与引用类型、参数传递组合考点

for-each 与引用类型、参数传递组合考点

for-each 与引用类型、参数传递组合考点

for-each 与引用类型、参数传递组合考点

for-each 与引用类型、参数传递组合考点

欢迎关注公众号。最后添加两个关于嵌套循环 nested loop 讲解的视频,希望对大家有帮助。(应该放数组相关的题目的,但是我还没有录制,所以上面放了几张图)。

巴朗计算机是一本非常好的教材,事无巨细。但是,缺点也是太详细了,有些枯燥。学生并不能看到 Java 在实际场景中的应用,所以如果能过结合 processing 或者代码本色,其实是个非常好的选择。

for-each 与引用类型、参数传递组合考点

 

这本书里面的案例都非常的酷炫,适合真正对编程感兴趣和队员仿真感兴趣的学生。

上一篇

国际高中IGCSE课程难度如何?

下一篇

2024年QS排名将更新指标!四大排名究竟参考谁?

你也可能喜欢

评论已经被关闭。

插入图片
老师微信 老师微信
老师微信
返回顶部