流程控制
输入和输出
输出
在前面的代码中,我们总是使用System.out.println()
来向屏幕输出一些内容。
println
是print line的缩写,表示输出并换行。因此,如果输出后不想换行,可以用print()
:
public class Main {
public static void main(String[] args) {
System.out.print("A,");
System.out.print("B,");
System.out.print("C.");
System.out.println();
System.out.println("END");
}
}
2
3
4
5
6
7
8
9
注意观察上述代码的执行效果。
格式化输出
Java还提供了格式化输出的功能。为什么要格式化输出?因为计算机表示的数据不一定适合人来阅读:
public class Main {
public static void main(String[] args) {
double d = 12900000;
System.out.println(d); // 1.29E7
}
}
2
3
4
5
6
如果要把数据显示成我们期望的格式,就需要使用格式化输出的功能。格式化输出使用System.out.printf()
,通过使用占位符%?
,printf()
可以把后面的参数格式化成指定格式:
public class Main {
public static void main(String[] args) {
double d = 3.1415926;
System.out.printf("%.2f\n", d); // 显示两位小数3.14
System.out.printf("%.4f\n", d); // 显示4位小数3.1416
}
}
2
3
4
5
6
7
Java的格式化功能提供了多种占位符,可以把各种数据类型“格式化”成指定的字符串:
占位符 | 说明 |
---|---|
%d | 格式化输出整数 |
%x | 格式化输出十六进制整数 |
%f | 格式化输出浮点数 |
%e | 格式化输出科学计数法表示的浮点数 |
%s | 格式化字符串 |
注意,由于%表示占位符,因此,连续两个%%表示一个%字符本身。
占位符本身还可以有更详细的格式化参数。下面的例子把一个整数格式化成十六进制,并用0补足8位:
public class Main {
public static void main(String[] args) {
int n = 12345000;
System.out.printf("n=%d, hex=%08x", n, n); // 注意,两个%占位符必须传入两个数
}
}
2
3
4
5
6
详细的格式化参数请参考JDK文档java.util.Formatter
输入
和输出相比,Java的输入就要复杂得多。
我们先看一个从控制台读取一个字符串和一个整数的例子:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
2
3
4
5
6
7
8
9
10
11
12
首先,我们通过import
语句导入java.util.Scanner
,import
是导入某个类的语句,必须放到Java源代码的开头,后面我们在Java的package
中会详细讲解如何使用import
。
然后,创建Scanner
对象并传入System.in
。System.out
代表标准输出流,而System.in
代表标准输入流。直接使用System.in
读取用户输入虽然是可以的,但需要更复杂的代码,而通过Scanner
就可以简化后续的代码。
有了Scanner
对象后,要读取用户输入的字符串,使用scanner.nextLine()
,要读取用户输入的整数,使用scanner.nextInt()
。Scanner会自动转换数据类型,因此不必手动转换。
要测试输入,我们不能在线运行它,因为输入必须从命令行读取,因此,需要走编译、执行的流程:
$ javac Main.java
这个程序编译时如果有警告,可以暂时忽略它,在后面学习IO的时候再详细解释。编译成功后,执行:
$ java Main
Input your name: Bob
Input your age: 12
Hi, Bob, you are 12
2
3
4
根据提示分别输入一个字符串和整数后,我们得到了格式化的输出。
练习
请帮小明同学设计一个程序,输入上次考试成绩(int)和本次考试成绩(int),然后输出成绩提高的百分比,保留两位小数位(例如,21.75%)。
小结
Java提供的输出包括:System.out.println()
/ print()
/ printf()
,其中printf()
可以格式化输出;
Java提供Scanner对象来方便输入,读取对应的类型可以使用:scanner.nextLine()
/ nextInt()
/ nextDouble()
/ ...
if判断
在Java程序中,如果要根据条件来决定是否执行某一段代码,就需要if语句。 if
语句的基本语法是:
if (条件) {
// 条件满足时执行
}
2
3
根据if
的计算结果(true
还是false
),JVM决定是否执行if
语句块(即花括号{}包含的所有语句)。
让我们来看一个例子:
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 60) {
System.out.println("及格了");
}
System.out.println("END");
}
}
2
3
4
5
6
7
8
9
当条件n >= 60
计算结果为true
时,if
语句块被执行,将打印"及格了"
,否则,if
语句块将被跳过。修改n
的值可以看到执行效果。
注意到if
语句包含的块可以包含多条语句:
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 60) {
System.out.println("及格了");
System.out.println("恭喜你");
}
System.out.println("END");
}
}
2
3
4
5
6
7
8
9
10
当if
语句块只有一行语句时,可以省略花括号{}:
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 60)
System.out.println("及格了");
System.out.println("END");
}
}
2
3
4
5
6
7
8
但是,省略花括号并不总是一个好主意。假设某个时候,突然想给if
语句块增加一条语句时:
public class Main {
public static void main(String[] args) {
int n = 50;
if (n >= 60)
System.out.println("及格了");
System.out.println("恭喜你"); // 注意这条语句不是if语句块的一部分
System.out.println("END");
}
}
2
3
4
5
6
7
8
9
由于使用缩进格式,很容易把两行语句都看成if
语句的执行块,但实际上只有第一行语句是if
的执行块。在使用git这些版本控制系统自动合并时更容易出问题,所以不推荐忽略花括号的写法。
else
if
语句还可以编写一个else { ... }
,当条件判断为false
时,将执行else
的语句块:
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 60) {
System.out.println("及格了");
} else {
System.out.println("挂科了");
}
System.out.println("END");
}
}
2
3
4
5
6
7
8
9
10
11
修改上述代码n
的值,观察if
条件为true
或false
时,程序执行的语句块。
注意,else
不是必须的。
还可以用多个if ... else if ...
串联。例如:
public class Main {
public static void main(String[] args) {
int n = 70;
if (n >= 90) {
System.out.println("优秀");
} else if (n >= 60) {
System.out.println("及格了");
} else {
System.out.println("挂科了");
}
System.out.println("END");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
串联的效果其实相当于:
if (n >= 90) {
// n >= 90为true:
System.out.println("优秀");
} else {
// n >= 90为false:
if (n >= 60) {
// n >= 60为true:
System.out.println("及格了");
} else {
// n >= 60为false:
System.out.println("挂科了");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
在串联使用多个if
时,要_特别注意_判断顺序。观察下面的代码:
public class Main {
public static void main(String[] args) {
int n = 100;
if (n >= 60) {
System.out.println("及格了");
} else if (n >= 90) {
System.out.println("优秀");
} else {
System.out.println("挂科了");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
执行发现,n = 100
时,满足条件n >= 90
,但输出的不是"优秀"
,而是"及格了"
,原因是if
语句从上到下执行时,先判断n >= 60
成功后,后续else
不再执行,因此,if (n >= 90)
没有机会执行了。
正确的方式是按照判断范围从大到小依次判断:
// 从大到小依次判断:
if (n >= 90) {
// ...
} else if (n >= 60) {
// ...
} else {
// ...
}
2
3
4
5
6
7
8
或者改写成从小到大依次判断:
// 从小到大依次判断:
if (n < 60) {
// ...
} else if (n < 90) {
// ...
} else {
// ...
}
2
3
4
5
6
7
8
使用if
时,还要特别注意边界条件。例如:
public class Main {
public static void main(String[] args) {
int n = 90;
if (n > 90) {
System.out.println("优秀");
} else if (n >= 60) {
System.out.println("及格了");
} else {
System.out.println("挂科了");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
假设我们期望90分或更高为“优秀”,上述代码输出的却是“及格”,原因是>
和>=
效果是不同的。
前面讲过了浮点数在计算机中常常无法精确表示,并且计算可能出现误差,因此,判断浮点数相等用==
判断不靠谱:
public class Main {
public static void main(String[] args) {
double x = 1 - 9.0 / 10;
if (x == 0.1) {
System.out.println("x is 0.1");
} else {
System.out.println("x is NOT 0.1");
}
}
}
2
3
4
5
6
7
8
9
10
正确的方法是利用差值小于某个临界值来判断:
public class Main {
public static void main(String[] args) {
double x = 1 - 9.0 / 10;
if (Math.abs(x - 0.1) < 0.00001) {
System.out.println("x is 0.1");
} else {
System.out.println("x is NOT 0.1");
}
}
}
2
3
4
5
6
7
8
9
10
判断引用类型相等
在Java中,判断值类型的变量是否相等,可以使用==
运算符。但是,判断引用类型的变量是否相等,==
表示“引用是否相等”,或者说,是否指向同一个对象。例如,下面的两个String类型,它们的内容是相同的,但是,分别指向不同的对象,用==
判断,结果为false
:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1);
System.out.println(s2);
if (s1 == s2) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
要判断引用类型的变量内容是否相等,必须使用equals()
方法:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1);
System.out.println(s2);
if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
注意:执行语句s1.equals(s2)
时,如果变量s1
为null
,会报NullPointerException
:
public class Main {
public static void main(String[] args) {
String s1 = null;
if (s1.equals("hello")) {
System.out.println("hello");
}
}
}
2
3
4
5
6
7
8
要避免NullPointerException
错误,可以利用短路运算符&&
:
public class Main {
public static void main(String[] args) {
String s1 = null;
if (s1 != null && s1.equals("hello")) {
System.out.println("hello");
}
}
}
2
3
4
5
6
7
8
还可以把一定不是null
的对象"hello"
放到前面:例如:if ("hello".equals(s)) { ... }
。
练习
请用if ... else
编写一个程序,用于计算体质指数BMI,并打印结果。
BMI = 体重(kg)除以身高(m)的平方
BMI结果:
- 过轻:低于18.5
- 正常:18.5-25
- 过重:25-28
- 肥胖:28-32
- 非常肥胖:高于32
小结
if ... else
可以做条件判断,else
是可选的;
不推荐省略花括号{}
;
多个if ... else
串联要特别注意判断顺序;
要注意if的边界条件;
要注意浮点数判断相等不能直接用==
运算符;
引用类型判断内容相等要使用equals()
,注意避免NullPointerException
。
switch多重选择
除了if语句外,还有一种条件判断,是根据某个表达式的结果,分别去执行不同的分支。
例如,在游戏中,让用户选择选项:
- 单人模式
- 多人模式
- 退出游戏
这时,switch
语句就派上用场了。
switch
语句根据switch (表达式)
计算的结果,跳转到匹配的case
结果,然后继续执行后续语句,直到遇到break
结束执行。
我们看一个例子:
public class Main {
public static void main(String[] args) {
int option = 1;
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
System.out.println("Selected 2");
break;
case 3:
System.out.println("Selected 3");
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
修改option
的值分别为1
、2
、3
,观察执行结果。
如果option
的值没有匹配到任何case
,例如option = 99
,那么,switch
语句不会执行任何语句。这时,可以给switch
语句加一个default
,当没有匹配到任何case
时,执行default
:
public class Main {
public static void main(String[] args) {
int option = 99;
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
System.out.println("Selected 2");
break;
case 3:
System.out.println("Selected 3");
break;
default:
System.out.println("Not selected");
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果把switch
语句翻译成if
语句,那么上述的代码相当于:
if (option == 1) {
System.out.println("Selected 1");
} else if (option == 2) {
System.out.println("Selected 2");
} else if (option == 3) {
System.out.println("Selected 3");
} else {
System.out.println("Not selected");
}
2
3
4
5
6
7
8
9
对于多个==
判断的情况,使用switch
结构更加清晰。
同时注意,上述“翻译”只有在switch
语句中对每个case
正确编写了break
语句才能对应得上。
使用switch
时,注意case
语句并没有花括号{}
,而且,case
语句具有“穿透性”,漏写break
将导致意想不到的结果:
public class Main {
public static void main(String[] args) {
int option = 2;
switch (option) {
case 1:
System.out.println("Selected 1");
case 2:
System.out.println("Selected 2");
case 3:
System.out.println("Selected 3");
default:
System.out.println("Not selected");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当option = 2
时,将依次输出"Selected 2"
、"Selected 3"
、"Not selected"
,原因是从匹配到case 2
开始,后续语句将全部执行,直到遇到break
语句。因此,任何时候都不要忘记写break
。
如果有几个case
语句执行的是同一组语句块,可以这么写:
public class Main {
public static void main(String[] args) {
int option = 2;
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
case 3:
System.out.println("Selected 2, 3");
break;
default:
System.out.println("Not selected");
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用switch
语句时,只要保证有break
,case
的顺序不影响程序逻辑:
switch (option) {
case 3:
...
break;
case 2:
...
break;
case 1:
...
break;
}
2
3
4
5
6
7
8
9
10
11
但是仍然建议按照自然顺序排列,便于阅读。
switch
语句还可以匹配字符串。字符串匹配时,是比较“内容相等”。例如:
public class Main {
public static void main(String[] args) {
String fruit = "apple";
switch (fruit) {
case "apple":
System.out.println("Selected apple");
break;
case "pear":
System.out.println("Selected pear");
break;
case "mango":
System.out.println("Selected mango");
break;
default:
System.out.println("No fruit selected");
break;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch
语句还可以使用枚举类型,枚举类型我们在后面讲解。
编译检查
使用IDE时,可以自动检查是否漏写了break
语句和default
语句,方法是打开IDE的编译检查。
在Eclipse中,选择Preferences
- Java
- Compiler
- Errors/Warnings
- Potential programming problems
,将以下检查标记为Warning:
- 'switch' is missing 'default' case
- 'switch' case fall-through
在Idea中,选择Preferences
- Editor
- Inspections
- Java
- Control flow issues
,将以下检查标记为Warning:
- Fallthrough in 'switch' statement
- 'switch' statement without 'default' branch
当switch
语句存在问题时,即可在IDE中获得警告提示。
switch表达式
使用switch
时,如果遗漏了break
,就会造成严重的逻辑错误,而且不易在源代码中发现错误。从Java 12开始,switch
语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break
语句:
public class Main {
public static void main(String[] args) {
String fruit = "apple";
switch (fruit) {
case "apple" -> System.out.println("Selected apple");
case "pear" -> System.out.println("Selected pear");
case "mango" -> {
System.out.println("Selected mango");
System.out.println("Good choice!");
}
default -> System.out.println("No fruit selected");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意新语法使用->
,如果有多条语句,需要用{}
括起来。不要写break
语句,因为新语法只会执行匹配的语句,没有穿透效应。
很多时候,我们还可能用switch
语句给某个变量赋值。例如:
int opt;
switch (fruit) {
case "apple":
opt = 1;
break;
case "pear":
case "mango":
opt = 2;
break;
default:
opt = 0;
break;
}
2
3
4
5
6
7
8
9
10
11
12
13
使用新的switch
语法,不但不需要break
,还可以直接返回值。把上面的代码改写如下:
public class Main {
public static void main(String[] args) {
String fruit = "apple";
int opt = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> 0;
}; // 注意赋值语句要以;结束
System.out.println("opt = " + opt);
}
}
2
3
4
5
6
7
8
9
10
11
这样可以获得更简洁的代码。
yield
大多数时候,在switch
表达式内部,我们会返回简单的值。
但是,如果需要复杂的语句,我们也可以写很多语句,放到{...}
里,然后,用yield
返回一个值作为switch
语句的返回值:
public class Main {
public static void main(String[] args) {
String fruit = "orange";
int opt = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> {
int code = fruit.hashCode();
yield code; // switch语句返回值
}
};
System.out.println("opt = " + opt);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
小结
switch
语句可以做多重选择,然后执行匹配的case
语句后续代码;
switch
的计算结果必须是整型、字符串或枚举类型;
注意千万不要漏写break
,建议打开fall-through
警告;
总是写上default
,建议打开missing default
警告;
从Java 14开始,switch
语句正式升级为表达式,不再需要break
,并且允许使用yield
返回值。
while循环
循环语句就是让计算机根据条件做循环计算,在条件满足时继续循环,条件不满足时退出循环。
例如,计算从1到100的和:
1 + 2 + 3 + 4 + … + 100 = ?
除了用数列公式外,完全可以让计算机做100次循环累加。因为计算机的特点是计算速度非常快,我们让计算机循环一亿次也用不到1秒,所以很多计算的任务,人去算是算不了的,但是计算机算,使用循环这种简单粗暴的方法就可以快速得到结果。
我们先看Java提供的while
条件循环。它的基本用法是:
while (条件表达式) {
循环语句
}
// 继续执行后续代码
2
3
4
while
循环在每次循环开始前,首先判断条件是否成立。如果计算结果为true
,就把循环体内的语句执行一遍,如果计算结果为false
,那就直接跳到while
循环的末尾,继续往下执行。
我们用while循环来累加1到100,可以这么写:
public class Main {
public static void main(String[] args) {
int sum = 0; // 累加的和,初始化为0
int n = 1;
while (n <= 100) { // 循环条件是n <= 100
sum = sum + n; // 把n累加到sum中
n ++; // n自身加1
}
System.out.println(sum); // 5050
}
}
2
3
4
5
6
7
8
9
10
11
注意到while
循环是先判断循环条件,再循环,因此,有可能一次循环都不做。
对于循环条件判断,以及自增变量的处理,要特别注意边界条件。思考一下下面的代码为何没有获得正确结果:
public class Main {
public static void main(String[] args) {
int sum = 0;
int n = 0;
while (n <= 100) {
n ++;
sum = sum + n;
}
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
10
11
如果循环条件永远满足,那这个循环就变成了死循环。死循环将导致100%的CPU占用,用户会感觉电脑运行缓慢,所以要避免编写死循环代码。
如果循环条件的逻辑写得有问题,也会造成意料之外的结果:
public class Main {
public static void main(String[] args) {
int sum = 0;
int n = 1;
while (n > 0) {
sum = sum + n;
n ++;
}
System.out.println(n); // -2147483648
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
10
11
12
表面上看,上面的while
循环是一个死循环,但是,Java的int
类型有最大值,达到最大值后,再加1会变成负数,结果,意外退出了while
循环。
练习
使用while
计算从m
到n
的和:
public class Main {
public static void main(String[] args) {
int sum = 0;
int m = 20;
int n = 100;
// 使用while计算M+...+N:
while (m <= n) {
sum += m;
m++;
}
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
小结
while
循环先判断循环条件是否满足,再执行循环语句;
while
循环可能一次都不执行;
编写循环时要注意循环条件,并避免死循环。
do while循环
在Java中,while
循环是先判断循环条件,再执行循环。而另一种do while
循环则是先执行循环,再判断条件,条件满足时继续循环,条件不满足时退出。它的用法是:
do {
执行循环语句
} while (条件表达式);
2
3
可见,do while
循环会至少循环一次。
我们把对1到100的求和用do while
循环改写一下:
public class Main {
public static void main(String[] args) {
int sum = 0;
int n = 1;
do {
sum = sum + n;
n ++;
} while (n <= 100);
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
10
11
使用do while
循环时,同样要注意循环条件的判断。
练习
使用do while
循环计算从m
到n
的和。
public class Main {
public static void main(String[] args) {
int sum = 0;
int m = 20;
int n = 100;
// 使用do while计算M+...+N:
do {
sum = sum + m;
m++;
} while (m <= n);
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
小结
do while
循环先执行循环,再判断条件;
do while
循环会至少执行一次。
for循环
除了while
和do while
循环,Java使用最广泛的是for
循环。
for
循环的功能非常强大,它使用计数器实现循环。for
循环会先初始化计数器,然后,在每次循环前检测循环条件,在每次循环后更新计数器。计数器变量通常命名为i
。
我们把1到100求和用for
循环改写一下:
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i=1; i<=100; i++) {
sum = sum + i;
}
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
在for
循环执行前,会先执行初始化语句int i=1
,它定义了计数器变量i
并赋初始值为1
,然后,循环前先检查循环条件i<=100
,循环后自动执行i++
,因此,和while
循环相比,for
循环把更新计数器的代码统一放到了一起。在for
循环的循环体内部,不需要去更新变量i
。
因此,for
循环的用法是:
for (初始条件; 循环检测条件; 循环后更新计数器) {
// 执行语句
}
2
3
如果我们要对一个整型数组的所有元素求和,可以用for
循环实现:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
int sum = 0;
for (int i=0; i<ns.length; i++) {
System.out.println("i = " + i + ", ns[i] = " + ns[i]);
sum = sum + ns[i];
}
System.out.println("sum = " + sum);
}
}
2
3
4
5
6
7
8
9
10
11
上面代码的循环条件是i<ns.length
。因为ns
数组的长度是5
,因此,当循环5
次后,i的值被更新为5
,就不满足循环条件,因此for
循环结束。
思考:如果把循环条件改为i<=ns.length,会出现什么问题?
注意for
循环的初始化计数器总是会被执行,并且for
循环也可能循环0次。
使用for
循环时,千万不要在循环体内修改计数器!在循环体中修改计数器常常导致莫名其妙的逻辑错误。对于下面的代码:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
i = i + 1;
}
}
}
2
3
4
5
6
7
8
9
虽然不会报错,但是,数组元素只打印了一半,原因是循环内部的i = i + 1
导致了计数器变量每次循环实际上加了2
(因为for
循环还会自动执行i++
)。因此,在for
循环中,不要修改计数器的值。计数器的初始化、判断条件、每次循环后的更新条件统一放到for()
语句中可以一目了然。
如果希望只访问索引为奇数的数组元素,应该把for
循环改写为:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i=i+2) {
System.out.println(ns[i]);
}
2
3
4
通过更新计数器的语句i=i+2
就达到了这个效果,从而避免了在循环体内去修改变量i
。
使用for
循环时,计数器变量i
要尽量定义在for
循环中:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
// 无法访问i
int n = i; // compile error!
2
3
4
5
6
如果变量i定义在for
循环外:
int[] ns = { 1, 4, 9, 16, 25 };
int i;
for (i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
// 仍然可以使用i
int n = i;
2
3
4
5
6
7
那么,退出for
循环后,变量i
仍然可以被访问,这就破坏了变量应该把访问范围缩到最小的原则。
灵活使用for循环
for
循环还可以缺少初始化语句、循环条件和每次循环更新语句,例如:
// 不设置结束条件:
for (int i=0; ; i++) {
...
}
2
3
4
// 不设置结束条件和更新语句:
for (int i=0; ;) {
...
}
2
3
4
// 什么都不设置:
for (;;) {
...
}
2
3
4
通常不推荐这样写,但是,某些情况下,是可以省略for
循环的某些语句的。
for each循环
for
循环经常用来遍历数组,因为通过计数器可以根据索引来访问数组的每个元素:
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
2
3
4
但是,很多时候,我们实际上真正想要访问的是数组每个元素的值。Java还提供了另一种for each
循环,它可以更简单地遍历数组:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
2
3
4
5
6
7
8
和for
循环相比,for each
循环的变量n不再是计数器,而是直接对应到数组的每个元素。for each
循环的写法也更简洁。但是,for each
循环无法指定遍历顺序,也无法获取数组的索引。
除了数组外,for each
循环能够遍历所有“可迭代”的数据类型,包括后面会介绍的List
、Map
等。
练习1
给定一个数组,请用for
循环倒序输出每一个元素:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int i = ns.length - 1; i >= 0; i--) {
System.out.println(ns[i]);
}
}
}
2
3
4
5
6
7
8
练习2
利用for each
循环对数组每个元素求和:
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
int sum = 0;
for (int n : ns) {
sum += n;
}
System.out.println(sum); // 55
}
}
2
3
4
5
6
7
8
9
10
练习3
圆周率π可以使用公式计算:
请利用for
循环计算π:
public class Main {
public static void main(String[] args) {
double pi = 0;
for (int i = 1; i < 100000000; i++) {
// TODO
pi += ((i % 2) == 0 ? -1.0 : 1.0) / (2 * i - 1);
}
pi *= 4;
System.out.println(pi);
}
}
2
3
4
5
6
7
8
9
10
11
小结
for
循环通过计数器可以实现复杂循环;
for each
循环可以直接遍历数组的每个元素;
最佳实践:计数器变量定义在for
循环内部,循环体内部不修改计数器;
break和continue
无论是while
循环还是for
循环,有两个特别的语句可以使用,就是break
语句和continue
语句。
break
在循环过程中,可以使用break
语句跳出当前循环。我们来看一个例子:
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i=1; ; i++) {
sum = sum + i;
if (i == 100) {
break;
}
}
System.out.println(sum);
}
}
2
3
4
5
6
7
8
9
10
11
12
使用for
循环计算从1到100时,我们并没有在for()
中设置循环退出的检测条件。但是,在循环内部,我们用if
判断,如果i==100
,就通过break
退出循环。
因此,break
语句通常都是配合if
语句使用。要特别注意,break
语句总是跳出自己所在的那一层循环。例如:
public class Main {
public static void main(String[] args) {
for (int i=1; i<=10; i++) {
System.out.println("i = " + i);
for (int j=1; j<=10; j++) {
System.out.println("j = " + j);
if (j >= i) {
break;
}
}
// break跳到这里
System.out.println("breaked");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面的代码是两个for
循环嵌套。因为break
语句位于内层的for循环,因此,它会跳出内层for
循环,但不会跳出外层for
循环。
continue
break
会跳出当前循环,也就是整个循环都不会执行了。而continue
则是提前结束本次循环,直接继续执行下次循环。我们看一个例子:
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i=1; i<=10; i++) {
System.out.println("begin i = " + i);
if (i % 2 == 0) {
continue; // continue语句会结束本次循环
}
sum = sum + i;
System.out.println("end i = " + i);
}
System.out.println(sum); // 25
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意观察continue
语句的效果。当i
为奇数时,完整地执行了整个循环,因此,会打印begin i=1
和end i=1
。在i
为偶数时,continue
语句会提前结束本次循环,因此,会打印begin i=2
但不会打印end i = 2
。
在多层嵌套的循环中,continue
语句同样是结束本次自己所在的循环。
小结
break
语句可以跳出当前循环;
break
语句通常配合if
,在满足条件时提前结束整个循环;
break
语句总是跳出最近的一层循环;
continue
语句可以提前结束本次循环;
continue
语句通常配合if
,在满足条件时提前结束本次循环。
评论区留言准则:
1. 本评论区禁止传播封建迷信、吸烟酗酒、低俗色情、赌博诈骗等任何违法违规内容。
2. 当他人以不正当方式诱导打赏、私下交易,请谨慎判断,以防人身财产损失。
3. 请勿轻信各类招聘征婚、代练代抽、私下交易、购买礼包码、游戏币等广告信息,谨防网络诈骗。