Java从环境到类与对象 · SSH 实操

第二单元 · 分支、循环、数组 · 第 5 / 12 节

for 与 while:重复做一件事

写 for 三段式与 while 循环,用 break 提前结束、continue 跳过本次,写浅层嵌套并解释内外层执行次数,避免死循环。

约 35 分钟 · 7 个步骤 · 进度只存本机

这一节继续“左边网页、右边终端”。网页给完整源码和命令,终端里真的编译、运行、看输出。$ 开头的是 shell 命令;java 代码块是完整源文件;output 是程序标准输出;本节没有 input 块,所有例子都用写死的变量。

第一单元你已经会变量、算术、比较和 Scanner;上一节会 if。这一节把“做一次”变成“重复做”:for、while、do-while,再用 break 提前结束、continue 跳过本次。本节不用数组和自定义方法;main 仍然只是入口外壳。

新开 SSH 窗口时,先 source ~/java-practice/env.sh,确认 java -version 和 javac -version 都是 17.x。本节命令默认在同一个窗口里执行;source 可能打印一行环境提示,那是 shell 的输出,不要和程序 output 混。

SSH 服务器终端Java 源码程序交互输入输出示例(只读)复制 Java 源码不带文件名;复制命令不带 $ 提示符;输出和输入示例没有复制按钮。
这是教程网页,不是在线运行环境。Java 代码和 SSH 命令都要在你的服务器上执行;网页不会执行代码、不会连接服务器、也不会接收密码。

已完成 0 / 7 步

第 1 步:for 的三段式,数着次数重复

for 适合“次数已知”的重复:

示例for 语法骨架
for (初始化; 条件; 更新) {
    循环体;
}

执行顺序不是“初始化→条件→更新→循环体”,而是:

  1. 初始化只做一次;
  2. 判断条件,true 才进入循环体;
  3. 循环体执行完,再做更新;
  4. 回到第 2 步继续判断。

先预测:下面的程序会打印几次“第 N 次”?如果把 i <= 5 改成 i < 5,会打印几次?

建目录、写 ForCountDemo.java。同名文件存在时先备份,不覆盖你上次的内容:

SSH 服务器终端bash
$ source ~/java-practice/env.sh
$ java -version
$ javac -version
$ mkdir -p ~/java-practice/lesson05
$ cd ~/java-practice/lesson05
$ if [ -e ForCountDemo.java ]; then mv ForCountDemo.java "ForCountDemo.java.bak.$(date +%s)"; fi
$ nano ForCountDemo.java

把下面这个完整文件粘进 nano,按 Ctrl+O 回车保存,再按 Ctrl+X 退出:

Java 源码ForCountDemo.java
public class ForCountDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            System.out.println("第 " + i + " 次");
        }
        System.out.println("循环结束");
    }
}
SSH 服务器终端bash
$ ls -l ForCountDemo.java
$ javac -encoding UTF-8 ForCountDemo.java
$ java ForCountDemo
输出示例(只读)output不可复制
第 1 次
第 2 次
第 3 次
第 4 次
第 5 次
循环结束

i 从 1 开始,每次循环体结束后 i++,条件 i <= 5 在 1、2、3、4、5 时都为 true,所以循环体执行 5 次;i 变成 6 时条件为 false,循环结束,接着执行循环后面的“循环结束”。

i 是 for 里声明的变量,作用范围只在 for 的圆括号和花括号里;循环结束后再写 System.out.println(i); 会编译错误。现在不用记术语,先记住“i 出了循环就不能用”。

提示 先数三处

读 for 先看三个地方:int i = 1(从几开始)、i <= 5(到几停)、i++(每次加多少)。三处一起决定次数,不要只看条件。

参考答案 先预测:改成 i < 5

只打印 4 次(1 到 4),然后打印“循环结束”。因为 i 变成 5 时,5 < 5 是 false,第 5 次进不去。把 i++ 改成 i-- 则条件 i <= 5 会一直为 true(i 越来越小),程序进入死循环;这时在终端按 Ctrl+C 停掉,不要关服务器。

第 2 步:用累加器把结果带出循环

循环经常用来“累加”:先把结果变量初始化,再在循环体里一次次更新。比如求 1 到 100 的和:

示例累加骨架
int sum = 0;
for (...) {
    sum = sum + i;
}

sum = sum + i 先算右边的 sum + i,再把结果赋回左边的 sum。所以 sum 会保留上一次的值,越加越大。如果循环体里忘了写这行,sum 会一直是 0;如果把 int sum = 0; 写在循环体里面,每次循环都会把 sum 重置成 0,最后只剩最后一个数。

先预测:1 到 100 的和是多少?如果只加到 5,sum 最后是多少?

SSH 服务器终端bash
$ cd ~/java-practice/lesson05
$ if [ -e ForSumDemo.java ]; then mv ForSumDemo.java "ForSumDemo.java.bak.$(date +%s)"; fi
$ nano ForSumDemo.java
Java 源码ForSumDemo.java
public class ForSumDemo {
    public static void main(String[] args) {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum = sum + i;
        }
        System.out.println("1 到 100 的和是:" + sum);
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 ForSumDemo.java
$ java ForSumDemo
输出示例(只读)output不可复制
1 到 100 的和是:5050

sum 在循环前是 0;第 1 次加 1 变 1,第 2 次加 2 变 3,第 3 次加 3 变 6……循环结束后 sum 就是 5050。循环体里没有打印,所以中间过程看不见,只有最后一行输出。

把 i <= 100 改成 i <= 5,和会变成 15(1+2+3+4+5),但打印文字还写着“1 到 100”,记得一起改。先在小范围核对,再推广到 100,是检查循环边界的常用方法。

第 3 步:while 把“初始化、条件、更新”分开写

while 也能重复,但三个部分不挤在一行:

示例while 语法骨架
初始化;
while (条件) {
    循环体;
    更新;
}

for 把三部分写在圆括号里,适合次数明确的循环;while 把更新写在循环体里,适合“条件满足就一直做”的循环。两者可以互相改写,但 while 更容易漏掉更新。

先预测:下面的程序会打印哪些数字?如果删掉 i--; 这一行,会发生什么?

SSH 服务器终端bash
$ cd ~/java-practice/lesson05
$ if [ -e WhileCountDemo.java ]; then mv WhileCountDemo.java "WhileCountDemo.java.bak.$(date +%s)"; fi
$ nano WhileCountDemo.java
Java 源码WhileCountDemo.java
public class WhileCountDemo {
    public static void main(String[] args) {
        int i = 5;
        while (i >= 1) {
            System.out.println(i);
            i--;
        }
        System.out.println("发射!");
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 WhileCountDemo.java
$ java WhileCountDemo
输出示例(只读)output不可复制
5
4
3
2
1
发射!

i 从 5 开始,条件 i >= 1 为 true,打印 5,然后 i-- 变成 4;再判断、再打印……直到 i 变成 0,条件为 false,循环结束,打印“发射!”。

如果删掉 i--;,i 永远是 5,5 >= 1 永远为 true,程序会不停打印 5,这就是死循环。在终端按 Ctrl+C 可以停掉当前程序;不要用关闭服务器或杀其他进程的方式解决。

对比forwhile
初始化写在圆括号里,只执行一次写在循环前
条件圆括号中间圆括号里
更新圆括号最后循环体里自己写
适合次数明确条件满足就继续
注意 死循环的第一检查点

程序不停输出同一行、终端没有回到提示符,先看循环变量有没有更新。while 的更新写在循环体里,最容易漏;漏了就先 Ctrl+C,再补更新,不要反复运行。

第 4 步:do-while 先做一次,再判断

do-while 是“先执行循环体,再判断条件”:

示例do-while 语法骨架
do {
    循环体;
} while (条件);

注意 while (条件) 后面有分号。即使条件一开始就是 false,循环体也会执行一次;这是它和 while 的唯一区别。本阶段只要求认识它,不要求用它写复杂逻辑。

先预测:i = 10,条件 i < 3 一开始就是 false,循环体会执行几次?

SSH 服务器终端bash
$ cd ~/java-practice/lesson05
$ if [ -e DoWhileDemo.java ]; then mv DoWhileDemo.java "DoWhileDemo.java.bak.$(date +%s)"; fi
$ nano DoWhileDemo.java
Java 源码DoWhileDemo.java
public class DoWhileDemo {
    public static void main(String[] args) {
        int i = 10;
        do {
            System.out.println("执行一次,i = " + i);
            i++;
        } while (i < 3);
        System.out.println("结束,i = " + i);
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 DoWhileDemo.java
$ java DoWhileDemo
输出示例(只读)output不可复制
执行一次,i = 10
结束,i = 11

循环体先执行,打印 i = 10,然后 i++ 变成 11;这时才判断 11 < 3,结果为 false,循环结束。虽然条件一开始就不成立,循环体还是执行了一次。把 do-while 改成 while,同样的 i = 10 一次都不会执行。

第 5 步:break 提前结束整个循环

break 的作用是立刻跳出当前这一层循环,不再判断条件,也不执行循环体剩下的部分。常见用途:找到一个满足条件的数就停。

先预测:1 到 50 中,第一个 7 的倍数是多少?循环会执行多少次?

SSH 服务器终端bash
$ cd ~/java-practice/lesson05
$ if [ -e BreakDemo.java ]; then mv BreakDemo.java "BreakDemo.java.bak.$(date +%s)"; fi
$ nano BreakDemo.java
Java 源码BreakDemo.java
public class BreakDemo {
    public static void main(String[] args) {
        int target = -1;
        for (int i = 1; i <= 50; i++) {
            if (i % 7 == 0) {
                target = i;
                break;
            }
        }
        System.out.println("1 到 50 中第一个 7 的倍数是:" + target);
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 BreakDemo.java
$ java BreakDemo
输出示例(只读)output不可复制
1 到 50 中第一个 7 的倍数是:7

i 从 1 开始,前 6 次 i % 7 == 0 都是 false;i 到 7 时条件为 true,把 target 设为 7,break 立刻跳出 for,后面的 8 到 50 不再检查。target 在循环前初始化为 -1,是为了让变量在循环结束后仍然有值可打印;如果循环一次都没进,-1 就是一个“没找到”的标记。

break 只跳出离它最近的一层循环;如果是在 switch 里,它跳出的是 switch。本节只讨论循环里的 break。如果后面写嵌套循环,内层 break 不会把外层也停掉;这一点在第 7 步之后要记住。

参考答案 先预测:从 8 开始找

如果循环写成 for (int i = 8; i <= 50; i++),第一个满足 i % 7 == 0 的数是 14,所以输出 14。循环从 8 到 14 共执行 7 次,第 7 次才碰到 14。

第 6 步:continue 跳过本次,继续下一次

continue 不结束整个循环,它只跳过当前这一次循环体里剩下的语句,直接进入下一次的更新和条件判断。

先预测:1 到 10 中,把所有“能被 3 整除”的数跳过,剩下的数相加是多少?

SSH 服务器终端bash
$ cd ~/java-practice/lesson05
$ if [ -e ContinueDemo.java ]; then mv ContinueDemo.java "ContinueDemo.java.bak.$(date +%s)"; fi
$ nano ContinueDemo.java
Java 源码ContinueDemo.java
public class ContinueDemo {
    public static void main(String[] args) {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            if (i % 3 == 0) {
                continue;
            }
            sum = sum + i;
        }
        System.out.println("1 到 10 中不能被 3 整除的数的和:" + sum);
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 ContinueDemo.java
$ java ContinueDemo
输出示例(只读)output不可复制
1 到 10 中不能被 3 整除的数的和:37

i 为 3、6、9 时,i % 3 == 0 成立,continue 跳过 sum = sum + i;,直接进入下一次 i++;其他数正常累加。1+2+4+5+7+8+10 = 37。

在 for 里,continue 之后仍会执行圆括号里的 i++,所以不会死循环。在 while 里要特别小心:如果更新写在 continue 后面,continue 会跳过更新,循环变量不变,可能变成死循环。初学阶段先把更新写在循环体最前面,或改用 for。

关键字作用范围循环还继续吗
break跳出最近一层循环不继续,整个循环结束
continue跳过本次剩余语句继续下一次

第 7 步:浅层嵌套,内外层各跑几次

循环里再写一个循环叫嵌套。外层每走一次,内层都要从头到尾走一遍。下面用两层循环打印 3 行、每行 4 个星号:

SSH 服务器终端bash
$ cd ~/java-practice/lesson05
$ if [ -e NestedDemo.java ]; then mv NestedDemo.java "NestedDemo.java.bak.$(date +%s)"; fi
$ nano NestedDemo.java
Java 源码NestedDemo.java
public class NestedDemo {
    public static void main(String[] args) {
        String line = "";
        for (int row = 1; row <= 3; row++) {
            line = "";
            for (int col = 1; col <= 4; col++) {
                line = line + "*";
            }
            System.out.println(line);
        }
        System.out.println("共 3 行,每行 4 个星");
    }
}
SSH 服务器终端bash
$ javac -encoding UTF-8 NestedDemo.java
$ java NestedDemo
输出示例(只读)output不可复制
****
****
****
共 3 行,每行 4 个星

外层 row 从 1 到 3,共 3 次;每次外层循环体里,先把 line 清空,再让内层 col 从 1 到 4 走 4 次,每次往 line 后面接一个 *。内层结束后 line 是 "****",打印出来并换行。所以总共打印 3 行,每行 4 个星,内层循环体一共执行 3 × 4 = 12 次。

先预测:如果把外层改成 row <= 2、内层改成 col <= 5,会打印几行、每行几个星?内层循环体总共执行多少次?

参考答案 先预测:2 行 5 列

会打印 2 行,每行 5 个星;内层循环体执行 2 × 5 = 10 次。外层每走一次,内层都从 col = 1 重新开始,所以不是 5 次,而是 10 次。

注意 嵌套里的 break 只跳一层

如果在内层循环里写 break,只会结束内层,外层还会继续下一行。想让两层都停,需要在外层也判断并 break,或者用一个标记变量;本阶段先不写复杂跳出,看到嵌套 break 时先确认它停的是哪一层。

常见错误

  • 现象: 循环多打印一次或少打印一次,比如 1 到 5 只打印了 4 次。原因: 边界写成了 i < 5 而不是 i <= 5,或初始值从 0 开始。先检查: 把 i 的初始值、条件、更新三处列出来,手工走前两次和最后一次。
  • 现象: for 或 while 后面多了一个分号,循环体只执行一次或根本没按预期重复。原因: for (...); 或 while (...); 让循环体变成空语句,后面的花括号不再属于循环。先检查: 看右括号后有没有多余的 ;。
  • 现象: 程序不停输出同一行,终端回不到提示符。原因: 循环变量没有更新,或更新方向写反(该加却减),条件永远为 true。先检查: 按 Ctrl+C 停掉,再找 i++/i-- 是否在循环体里、是否被 continue 跳过。
  • 现象: 嵌套循环里 break 后,外层还在继续跑。原因: break 只跳出最近的一层,不会自动跳出外层。先检查: 确认 break 写在哪一层花括号里;需要两层都停时,用标记变量或在外层加条件。
  • 现象: while 里用了 continue 后程序卡住。原因: continue 跳过了后面的 i++,循环变量不变,条件永远成立。先检查: 把更新移到 continue 之前,或改用 for。
  • 现象: javac 报 cannot find symbol,指向循环结束后的 i。原因: for (int i = ...) 里的 i 只在循环内可见,出了循环就没了。先检查: 如果循环后还要用 i,把 int i; 声明在循环外面,循环里只写 i = 1。

练习

先自己写、先预测,再展开答案。练习答案都在 JDK 17 上实际编译运行过,output 是真实标准输出。

练习 1:求 1 到 20 的偶数和

用 for 和 if(或 continue)求 1 到 20 中所有偶数的和,输出一行结果。先预测结果,再写代码。

参考答案 练习 1:偶数和

完整文件存成 EvenSumDemo.java:

Java 源码EvenSumDemo.java
public class EvenSumDemo {
    public static void main(String[] args) {
        int sum = 0;
        for (int i = 1; i <= 20; i++) {
            if (i % 2 == 0) {
                sum = sum + i;
            }
        }
        System.out.println("1 到 20 中偶数的和:" + sum);
    }
}

编译运行:

SSH 服务器终端bash
$ javac -encoding UTF-8 EvenSumDemo.java
$ java EvenSumDemo
输出示例(只读)output不可复制
1 到 20 中偶数的和:110

2+4+6+…+20 = 110。i % 2 == 0 为 true 时才累加;也可以把条件反过来,用 continue 跳过奇数,结果一样。

练习 2:打印 4 行 3 列的井号

用两层 for 打印 4 行,每行 3 个 #,最后再打印一行“共 4 行,每行 3 个 #”。仍然用字符串拼接,不要用还没讲的 System.out.print。

参考答案 练习 2:4 行 3 列

完整文件存成 HashGridDemo.java:

Java 源码HashGridDemo.java
public class HashGridDemo {
    public static void main(String[] args) {
        String line = "";
        for (int row = 1; row <= 4; row++) {
            line = "";
            for (int col = 1; col <= 3; col++) {
                line = line + "#";
            }
            System.out.println(line);
        }
        System.out.println("共 4 行,每行 3 个 #");
    }
}

编译运行:

SSH 服务器终端bash
$ javac -encoding UTF-8 HashGridDemo.java
$ java HashGridDemo
输出示例(只读)output不可复制
###
###
###
###
共 4 行,每行 3 个 #

外层 4 次、内层每次 3 次,内层循环体共执行 12 次;每行开始前把 line 清空,所以不会把上一行的 # 带到下一行。

练习 3:先预测循环次数

下面的循环只是片段,不用单独存成文件;先口算会打印几行,再展开答案。

示例预测片段
int count = 0;
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 2; j++) {
        count++;
        System.out.println("i=" + i + ", j=" + j);
    }
}
System.out.println("count=" + count);
参考答案 练习 3:3 × 2

打印 6 行 i=..., j=...,最后一行 count=6。外层 i 有 3 个值,每个 i 下内层 j 都从 1 到 2 走 2 次,所以内层循环体执行 3 × 2 = 6 次,count 从 0 加到 6。

做到这里

做到这里今天可以停。你已经会:

  • 用 for 的三段式控制次数,并知道初始化、条件、更新的执行顺序;
  • 用 while 在条件成立时重复,知道更新漏写会死循环;
  • 认识 do-while 的“先做一次再判断”;
  • 用 break 提前结束、用 continue 跳过本次;
  • 写两层嵌套,并算清内外层各执行多少次。

下一节才学数组:把 5 个成绩放进一排带编号的格子,用下标和 length 遍历。本节还没有数组、没有自定义方法;练习里不要提前用数组或写 static 方法。

来源 来源与改编说明(非官方)

主题参考:黑马程序员 Java 入门到起飞(BV17F411T7Ao,2022-04-27)P045–P050 的 for、while、do-while、无限循环与跳转控制主题。本站为独立原创讲解,非官方站点/教材,未获授权,不逐字复刻视频或讲义;示例、错误清单与练习为原创补充。详见 source/SOURCE.md。

说明:本站是个人学习整理,不是官方教材;不包含原课程的书本、视频或受版权保护的整本内容。原课程与商标归其权利人。