在Java程序中JVM默认总是顺序执行以分号;结束的语句。但是在实际的代码中程序经常需要做条件判断、循环因此需要有多种流程控制语句来实现程序的跳转和循环等功能。输入和输出输出在前面的代码中我们总是使用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); } }注意观察上述代码的执行效果。格式化输出Java还提供了格式化输出的功能。为什么要格式化输出因为计算机表示的数据不一定适合人来阅读// 格式化输出 public class Main { public static void main(String[] args) { double d 12900000; System.out.println(d); // 1.29E7 } }如果要把数据显示成我们期望的格式就需要使用格式化输出的功能。格式化输出使用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 } }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); // 注意两个%占位符必须传入两个数 } }详细的格式化参数请参考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); // 格式化输出 } }首先我们通过import语句导入java.util.Scannerimport是导入某个类的语句必须放到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 ◀── 输入 Bob Input your age: 12 ◀── 输入 12 Hi, Bob, you are 12 ◀── 输出根据提示分别输入一个字符串和整数后我们得到了格式化的输出。if条件判断在Java程序中如果要根据条件来决定是否执行某一段代码就需要if语句。if语句的基本语法是if (条件) { // 条件满足时执行 }根据if的计算结果true还是falseJVM决定是否执行if语句块即花括号{}包含的所有语句。让我们来看一个例子// 条件判断 public class Main { public static void main(String[] args) { int n 70; if (n 60) { System.out.println(及格了); } System.out.println(END); } }当条件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); } }当if语句块只有一行语句时可以省略花括号{}// 条件判断 public class Main { public static void main(String[] args) { int n 70; if (n 60) System.out.println(及格了); System.out.println(END); } }但是省略花括号并不总是一个好主意。假设某个时候突然想给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); } }由于使用缩进格式很容易把两行语句都看成if语句的执行块但实际上只有第一行语句是if的执行块。在使用git这些版本控制系统自动合并时更容易出问题所以不推荐忽略花括号的写法。elseif语句还可以编写一个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); } }修改上述代码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); } }串联的效果其实相当于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(挂科了); } }在串联使用多个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(挂科了); } } }执行发现n 100时满足条件n 90但输出的不是优秀而是及格了原因是if语句从上到下执行时先判断n 60成功后后续else不再执行因此if (n 90)没有机会执行了。正确的方式是按照判断范围从大到小依次判断// 从大到小依次判断 if (n 90) { // ... } else if (n 60) { // ... } else { // ... }或者改写成从小到大依次判断// 从小到大依次判断 if (n 60) { // ... } else if (n 90) { // ... } else { // ... }使用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(挂科了); } } }假设我们期望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); } } }正确的方法是利用差值小于某个临界值来判断// 条件判断 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); } } }判断引用类型相等在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); } } }要判断引用类型的变量内容是否相等必须使用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); } } }注意执行语句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); } } }要避免NullPointerException错误可以利用短路运算符// 条件判断 public class Main { public static void main(String[] args) { String s1 null; if (s1 ! null s1.equals(hello)) { System.out.println(hello); } } }还可以把一定不是null的对象hello放到前面例如if (hello.equals(s)) { ... }。switch多重选择除了if语句外还有一种条件判断是根据某个表达式的结果分别去执行不同的分支。例如在游戏中让用户选择选项单人模式多人模式退出游戏这时switch语句就派上用场了。switch语句根据switch (表达式)计算的结果跳转到匹配的case结果然后继续执行后续语句直到遇到break结束执行。我们看一个例子// switch 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; } } }修改option的值分别为1、2、3观察执行结果。如果option的值没有匹配到任何case例如option 99那么switch语句不会执行任何语句。这时可以给switch语句加一个default当没有匹配到任何case时执行default// switch 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(Selected other); break; } } }如果把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(Selected other); }对比if ... else if语句对于多个判断的情况使用switch结构更加清晰。同时注意上述“翻译”只有在switch语句中对每个case正确编写了break语句才能对应得上。使用switch时注意case语句并没有花括号{}而且case语句具有“穿透性”漏写break将导致意想不到的结果// switch 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(Selected other); } } }当option 2时将依次输出Selected 2、Selected 3、Selected other原因是从匹配到case 2开始后续语句将全部执行直到遇到break语句。因此任何时候都不要忘记写break。如果有几个case语句执行的是同一组语句块可以这么写// switch 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(Selected other); break; } } }使用switch语句时只要保证有breakcase的顺序不影响程序逻辑switch (option) { case 3: ... break; case 2: ... break; case 1: ... break; }但是仍然建议按照自然顺序排列便于阅读。switch语句还可以匹配字符串。字符串匹配时是比较“内容相等”。例如// 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; } } }switch语句还可以使用枚举类型枚举类型我们在后面讲解。编译检查使用IDE时可以自动检查是否漏写了break语句和default语句方法是打开IDE的编译检查。在Eclipse中选择Preferences-Java-Compiler-Errors/Warnings-Potential programming problems将以下检查标记为Warningswitch is missing default case缺少default语句时警告switch case fall-through某个case缺少break时警告。在Idea中选择Preferences-Editor-Inspections-Java-Control flow issues将以下检查标记为Warningswitch statement without default branch缺少default语句时警告Fallthrough in switch statement某个case缺少break时警告。当switch语句存在问题时即可在IDE中获得警告提示。switch表达式使用switch时如果遗漏了break就会造成严重的逻辑错误而且不易在源代码中发现错误。从Java 12开始switch语句升级为更简洁的表达式语法使用类似模式匹配Pattern Matching的方法保证只有一种路径会被执行并且不需要break语句// switch 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); } } }注意新语法使用-如果有多条语句需要用{}括起来。不要写break语句因为新语法只会执行匹配的语句没有穿透效应。很多时候我们还可能用switch语句给某个变量赋值。例如int opt; switch (fruit) { case apple: opt 1; break; case pear: case mango: opt 2; break; default: opt 0; break; }使用新的switch语法不但不需要break还可以直接返回值。把上面的代码改写如下// switch 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); } }这样可以获得更简洁的代码。yield大多数时候在switch表达式内部我们会返回简单的值。但是如果需要复杂的语句我们也可以写很多语句放到{...}里然后用yield返回一个值作为switch语句的返回值// yield 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); } }while循环循环语句就是让计算机根据条件做循环计算在条件满足时继续循环条件不满足时退出循环。例如计算从1到100的和1 2 3 4 … 100 ?除了用数列公式外完全可以让计算机做100次循环累加。因为计算机的特点是计算速度非常快我们让计算机循环一亿次也用不到1秒所以很多计算的任务人去算是算不了的但是计算机算使用循环这种简单粗暴的方法就可以快速得到结果。我们先看Java提供的while条件循环。它的基本用法是while (条件表达式) { 循环语句 } // 继续执行后续代码while循环在每次循环开始前首先判断条件是否成立。如果计算结果为true就把循环体内的语句执行一遍如果计算结果为false那就直接跳到while循环的末尾继续往下执行。我们用while循环来累加1到100可以这么写// while 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 } }注意到while循环是先判断循环条件再循环因此有可能一次循环都不做。对于循环条件判断以及自增变量的处理要特别注意边界条件。思考一下下面的代码为何没有获得正确结果// 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); } }如果循环条件永远满足那这个循环就变成了死循环。死循环将导致100%的CPU占用用户会感觉电脑运行缓慢所以要避免编写死循环代码。如果循环条件的逻辑写得有问题也会造成意料之外的结果// while 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); } }表面上看上面的while循环是一个死循环但是Java的int类型有最大值达到最大值后再加1会变成负数结果意外退出了while循环。do while循环在Java中while循环是先判断循环条件再执行循环。而另一种do while循环则是先执行循环再判断条件条件满足时继续循环条件不满足时退出。它的用法是do { 执行循环语句 } while (条件表达式);可见do while循环会至少循环一次。我们把对1到100的求和用do while循环改写一下// 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); } }使用do while循环时同样要注意循环条件的判断。for循环除了while和do while循环Java使用最广泛的是for循环。for循环的功能非常强大它使用计数器实现循环。for循环会先初始化计数器然后在每次循环前检测循环条件在每次循环后更新计数器。计数器变量通常命名为i。我们把1到100求和用for循环改写一下// for public class Main { public static void main(String[] args) { int sum 0; for (int i1; i100; i) { sum sum i; } System.out.println(sum); } }在for循环执行前会先执行初始化语句int i1它定义了计数器变量i并赋初始值为1然后循环前先检查循环条件i100循环后自动执行i因此和while循环相比for循环把更新计数器的代码统一放到了一起。在for循环的循环体内部不需要去更新变量i。因此for循环的用法是for (初始条件; 循环检测条件; 循环后更新计数器) { // 执行语句 }如果我们要对一个整型数组的所有元素求和可以用for循环实现// for public class Main { public static void main(String[] args) { int[] ns { 1, 4, 9, 16, 25 }; int sum 0; for (int i0; ins.length; i) { System.out.println(i i , ns[i] ns[i]); sum sum ns[i]; } System.out.println(sum sum); } }上面代码的循环条件是ins.length。因为ns数组的长度是5因此当循环5次后i的值被更新为5就不满足循环条件因此for循环结束。思考如果把循环条件改为ins.length会出现什么问题注意for循环的初始化计数器总是会被执行并且for循环也可能循环0次。使用for循环时千万不要在循环体内修改计数器在循环体中修改计数器常常导致莫名其妙的逻辑错误。对于下面的代码// for public class Main { public static void main(String[] args) { int[] ns { 1, 4, 9, 16, 25 }; for (int i0; ins.length; i) { System.out.println(ns[i]); i i 1; } } }虽然不会报错但是数组元素只打印了一半原因是循环内部的i i 1导致了计数器变量每次循环实际上加了2因为for循环还会自动执行i。因此在for循环中不要修改计数器的值。计数器的初始化、判断条件、每次循环后的更新条件统一放到for()语句中可以一目了然。如果希望只访问索引号为偶数的数组元素应该把for循环改写为int[] ns { 1, 4, 9, 16, 25 }; for (int i0; ins.length; ii2) { System.out.println(ns[i]); }通过更新计数器的语句ii2就达到了这个效果从而避免了在循环体内去修改变量i。使用for循环时计数器变量i要尽量定义在for循环中int[] ns { 1, 4, 9, 16, 25 }; for (int i0; ins.length; i) { System.out.println(ns[i]); } // 无法访问i int n i; // compile error!如果变量i定义在for循环外int[] ns { 1, 4, 9, 16, 25 }; int i; for (i0; ins.length; i) { System.out.println(ns[i]); } // 仍然可以使用i int n i;那么退出for循环后变量i仍然可以被访问这就破坏了变量应该把访问范围缩到最小的原则。灵活使用for循环for循环还可以缺少初始化语句、循环条件和每次循环更新语句例如// 不设置结束条件: for (int i0; ; i) { ... }// 不设置结束条件和更新语句: for (int i0; ;) { ... }// 什么都不设置: for (;;) { ... }通常不推荐这样写但是某些情况下是可以省略for循环的某些语句的。for each循环for循环经常用来遍历数组因为通过计数器可以根据索引来访问数组的每个元素int[] ns { 1, 4, 9, 16, 25 }; for (int i0; ins.length; i) { System.out.println(ns[i]); }但是很多时候我们实际上真正想要访问的是数组每个元素的值。Java还提供了另一种for each循环它可以更简单地遍历数组// 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); } } }和for循环相比for each循环的变量n不再是计数器而是直接对应到数组的每个元素。for each循环的写法也更简洁。但是for each循环无法指定遍历顺序也无法获取数组的索引。除了数组外for each循环能够遍历所有“可迭代”的数据类型包括后面会介绍的List、Map等。break和continue无论是while循环还是for循环有两个特别的语句可以使用就是break语句和continue语句。break在循环过程中可以使用break语句跳出当前循环。我们来看一个例子// break public class Main { public static void main(String[] args) { int sum 0; for (int i1; ; i) { sum sum i; if (i 100) { break; } } System.out.println(sum); } }使用for循环计算从1到100时我们并没有在for()中设置循环退出的检测条件。但是在循环内部我们用if判断如果i100就通过break退出循环。因此break语句通常都是配合if语句使用。要特别注意break语句总是跳出自己所在的那一层循环。例如// break public class Main { public static void main(String[] args) { for (int i1; i10; i) { System.out.println(i i); for (int j1; j10; j) { System.out.println(j j); if (j i) { break; } } // break跳到这里 System.out.println(breaked); } } }上面的代码是两个for循环嵌套。因为break语句位于内层的for循环因此它会跳出内层for循环但不会跳出外层for循环。continuebreak会跳出当前循环也就是整个循环都不会执行了。而continue则是提前结束本次循环直接继续执行下次循环。我们看一个例子// continue public class Main { public static void main(String[] args) { int sum 0; for (int i1; i10; 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 } }注意观察continue语句的效果。当i为奇数时完整地执行了整个循环因此会打印begin i1和end i1。在i为偶数时continue语句会提前结束本次循环因此会打印begin i2但不会打印end i2。在多层嵌套的循环中continue语句同样是结束本次自己所在的循环。喜欢这个系列的同学可以关注我爆肝一个月Python 从入门到精通