文章目录
- 前言
- for 循环中的continue,break和return
- 实际业务中的滥用
- 总结
- 写在最后
前言
在最近的代码审查中,有帮忙审查了组里一个刚毕业1年不到的应届生,发现他写的其中一段代码将for循环中的break、continue、return滥用,导致了一个潜在的bug风险,这个风险后文我们再来分析。
for 循环中的continue,break和return
在讲解我实际碰到的业务例子之前,先来简单看看下面的例子
for循环是一种常用的编程结构,它可以重复执行一段代码,直到满足某个条件为止。在for循环中,有时我们需要根据不同的情况,跳过当前的迭代,结束当前的循环,或者结束整个方法。为了实现这些功能,Java语言提供了三个关键字:continue,break和return。
- continue关键字的作用是跳过当前的迭代,继续执行下一次的迭代。例如,如果我们想要打印出1到10之间的所有奇数,我们可以使用如下的代码:
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) { // 如果i是偶数,跳过当前的迭代
continue;
}
System.out.println(i); // 打印出i的值
}
输出结果为:
1
3
5
7
9
- break关键字的作用是结束当前的循环,跳出循环体。例如,如果我们想要打印出1到10之间的所有奇数,但是当i等于5时,就停止打印,我们可以使用如下的代码:
for (int i = 1; i <= 10; i++) {
if (i == 5) { // 如果i等于5,结束当前的循环
break;
}
if (i % 2 == 0) { // 如果i是偶数,跳过当前的迭代
continue;
}
System.out.println(i); // 打印出i的值
}
输出结果为:
1
3
- return关键字的作用是结束当前的方法,并返回一个值(如果有的话)。例如,如果我们想要计算1到10之间的所有奇数的和,我们可以使用如下的代码:
public static int sumOddNumbers() {
int sum = 0; // 初始化和为0
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) { // 如果i是偶数,跳过当前的迭代
continue;
}
sum += i; // 把i的值加到sum上
}
return sum; // 返回sum的值
}
调用该方法的结果为:
25
然而,在使用continue,break和return关键字时,我们需要注意一些潜在的bug。一个常见的bug是在for循环中使用return关键字,导致循环提前结束,而不是跳过当前的迭代。例如,如果我们想要打印出1到10之间的所有奇数,但是错误地使用了return关键字,我们会得到如下的代码:
我后面讲到的实际例子跟这个基本一样,就是错误滥用了return!
public static void printOddNumbers() {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) { // 如果i是偶数,错误地使用了return关键字
return;
}
System.out.println(i); // 打印出i的值
}
}
调用该方法的结果为:
1
这是因为当i等于2时,return关键字会结束整个方法,而不是跳过当前的迭代。这样,我们就无法打印出后面的奇数。为了避免这个bug,我们应该使用continue关键字,而不是return关键字。
实际业务中的滥用
上面那是很简单的例子,但在实际公司复杂的业务逻辑中,continue、break和return语句的误用可能导致更严重的错误,甚至难以追踪和修复。
接下来我给出我审查代码中的错误例子:
//定时任务每3分钟执行一次此方法
handleFileItemList(){
//查询出所有未处理的文件列表--- 文件标识flag=0未处理的
List<FileItem> fileItemList = getFileItemList();
//遍历列表,将对应的文件信息赋值到某个页面
for (FileItem item : fileItemList) {
......
if(满足此文件信息已经存在于对应页面){
//将此文件的flag标识置为已处理--flag=1
setFileItemFlag();
return;
}
.......
将对应文件信息赋值到某个页面
setFileInfo2Page();
//将此文件的flag标识置为已处理--flag=1
setFileItemFlag();
.......
}
}
注意这是一个定时任务的场景: 这里先从DB拉取一个文件信息表,这个文件表有个flag标识此文件是否已经处理过(flag:1已处理,0未处理),对表里面的List数据进行遍历,将对应的文件信息赋值到某个页面,他这里有个判断,如果某个文件信息已经存在于对应的页面了(除了定时任务场景,还会有别的场景会把文件信息赋值到页面),就停止循环,同时将此文件的flag标识置为已处理,直接return!
这个return处理方式说实话,如果一直没人去审查,也不会出现bug,因为针对这个场景是定时任务的,这里因为提前return了,导致后续的FileItem都无法进行处理,但是他会每3分钟执行一次,顶多是执行得慢一点,总能处理完!
,如果是别的场景,这个bug应该早就能发现了!
总结
continue,break和return关键字在for循环中的防止乱用的总结如下:
- continue关键字的作用是跳过当前的迭代,继续执行下一次的迭代。它可以用来跳过某些不需要执行的情况,或者优化循环的效率。
- break关键字的作用是结束当前的循环,跳出循环体。它可以用来提前终止循环,或者跳出嵌套的循环。
- return关键字的作用是结束当前的方法,并返回一个值(如果有的话)。它可以用来返回方法的结果,或者提前退出方法。
- 在for循环中使用return关键字,会导致循环提前结束,而不是跳过当前的迭代。这可能会导致逻辑错误,或者丢失部分结果。为了避免这个bug,我们应该使用continue关键字,而不是return关键字。
- 在嵌套的for循环中使用continue,break和return关键字,会导致循环的层级混乱。这可能会导致逻辑错误,或者输出错误的结果。为了避免这个bug,我们应该注意它们的作用范围,或者使用标签(label)来指定跳转的目标循环。
写在最后
-
代码这东西就是这样,一个return 、一个continue,实际上代表的意义千差万别。
-
像上面的bug,在普通的场景里面其实自己测试的时候可能就很好发现,但是如果在定时任务里面,不去好好审查一下代码,实际上不容易发现,因为最终文件信息都能去赋值到页面里面,只是慢一点而已。但是这也造成了一定的性能损失,明明一次定时任务就可以处理完所有文件,就因为return 的滥用,可能要好几次定时任务才能执行完毕。