Java-forEach增强for循环是值传递规则详解
1. 引入
正如Java语法意义,变量的传递只有值传递,虽然变量分为引用变量和基本类型变量,前者更像C中的地址概念。 在学习Lambda表达式的时候,遇到了试图在增强for循环中对原链表元素重新赋值失败的问题,网络上也没有针对此的其他博文,故开此文。
2. 数组的增强for循环
public class Test1{
public static void main(String[] args) {
int[] arr = new int[10];
for (int temp :arr){
temp++;
for (int temp :arr){
System.out.println(temp);
}
控制台会打出10个0,而不是1,这表明在
forEach
语句中
temp++
操作对
arr
数组本身没有任何影响,所以间接证明了,增强for循环中只是值传递。这也可以从原理层面解释:增强for循环作为一个语法糖,其执行顺序是:对数组第一个元素复制给临时变量temp,然后让temp执行循环中的语句;接着对数组第二个元素再次赋值给临时变量temp,再次让其执行for循环中的语句…就这般执行至数组最后一个元素。所以说,
temp
接受了数组元素的值,在
++
,这对于数组中的数字没有任何影响。所以说如果要进行原数组的更改,更好的方式是使用普通的for循环。
3. ArrayList的增强for循环
代码需求是将其list中的String类型对象从小写转换为大写;
public class LowercaseToUppercase{
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "hello world");
list.forEach(i -> {
i = i.toUpperCase();
list.forEach(System.out::println);
}
控制还是输出小写的String类型对象,“hello”, “world”, “hello world”,倘若你查看
forEach
方法,你可以发现此原理和第一个例子的数组遍历实现原理是一样的,
i
作为一个中间变量,是临时存放了String类型的引用变量,但是对原list没有任何影响,如下面被调用的forEach方法的默认实现代码(其中
t
就是被定义为
泛型类型T
的临时变量)。
一个易错点
:很多人认为:因为
String
内部是final修饰的数组,不能被重新赋值,临时变量
i
只能指向新的引用对象,所以上述代码功能才不能被实现,这是不对的,其真正的原因是对临时变量赋值是无法达到预期效果。
正确的理解是
:
对临时变量进行赋值,只能使临时变量指向新的对象,而对原String对象没有任何作用
。即使将上述代码中ArrayList的对象类型由
String
换成
StringBuilder
类,在这样的情况下,虽然同一个```StringBuilder``对象的值是可以被修改的,但是使用对临时变量赋值的操作还是不能对原数据结构元素值造成影响。
如果要实现,需要调用
StringBuilder
类对象的方法,一般是返回
this
对象,代码如下所示:
public class LowercaseToUppercase {
public static void main(String[] args) {
List<StringBuilder> list3= Arrays.asList(new StringBuilder("hello"),
new StringBuilder("world"),new StringBuilder("hello world"));
list3.forEach(i->
String str= i.toString().toUpperCase();
i.replace(0,str.length(),str);
list3.forEach(System.out::println);
}
控制台输出了大写的字符串,说明我们成功将
StringBuilder
类型由小写转化为大写,不过遍历中的临时变量
i
的赋值语句并不存在,而是调用其方法,返回this对象,才实现了转换。
下面这个代码块是Java集合的forEach方法默认实现,一定要读懂它:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);