乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

while 循环

"有些时候,你不知道循环会执行多少次——就像一个用户在登录界面反复输入密码,你无法预知他第几次才能输对。" —— 朱璐在实现飞翔云笔记登录模块时对实习生小崔解释,"这种'先判断再执行'的场景,while 循环是最自然的选择。"


概述

while 循环是"先判断,后执行"的循环结构。它会在每次循环体执行前检查条件——条件为 true 才进入循环体,为 false 则直接跳过。适合循环次数不确定但有明确终止条件的场景。

while 与 for 的定位

场景推荐结构原因
循环次数已知(如遍历数组)for三要素集中,结构紧凑
循环次数未知(如等待用户输入)while语义更清晰,"当...时就继续"
至少执行一次(如菜单交互)do-while保证首次执行

语法格式

while (布尔表达式) {
    // 循环体
}

关键点表格

关键点说明
条件判断每次循环开始前判断,true 则执行循环体,false 则退出
循环体可能 0 次如果首次判断条件即为 false,循环体一次也不执行
循环三要素初始化在 while 之前,条件在 while 括号内,迭代在循环体中
死循环while (true) { ... } 是合法的无限循环

执行流程

对比 for 循环:for 将三个要素集中在括号内;while 将初始化放在循环前、迭代放在循环体中。本质完全相同,只是写法不同。


示例一:用户登录重试

Frank 为飞翔云笔记设计了登录重试机制——用户最多可以尝试 3 次输入密码:

/**
 * 飞翔云笔记登录——while 循环(重试限制)
 */
import java.util.Scanner;

public class LoginRetry {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        final String CORRECT_PASSWORD = "feixiang2024";
        final int MAX_RETRIES = 3;

        int attempts = 0; // ① 初始化:已尝试次数

        System.out.println("===== 飞翔云笔记登录 =====");

        // ② 条件:未超过最大次数 且 未登录成功
        while (attempts < MAX_RETRIES) {
            System.out.print("请输入密码(剩余 "
                + (MAX_RETRIES - attempts) + " 次机会):");
            String input = scanner.nextLine();

            if (CORRECT_PASSWORD.equals(input)) {
                System.out.println("登录成功!欢迎使用飞翔云笔记。");
                break; // 登录成功,跳出循环
            }

            attempts++; // ③ 迭代:尝试次数 +1
            if (attempts < MAX_RETRIES) {
                System.out.println("密码错误,请重试。");
            }
        }

        if (attempts >= MAX_RETRIES) {
            System.out.println("已连续3次输入错误,账户已锁定。");
            System.out.println("请联系管理员:admin@feixiang.net");
        }

        scanner.close();
        System.out.println("登录流程结束。");
    }
}
运行输出(第3次成功):
===== 飞翔云笔记登录 =====
请输入密码(剩余 3 次机会):123456
密码错误,请重试。
请输入密码(剩余 2 次机会):password
密码错误,请重试。
请输入密码(剩余 1 次机会):feixiang2024
登录成功!欢迎使用飞翔云笔记。
登录流程结束。
运行输出(全部失败):
===== 飞翔云笔记登录 =====
请输入密码(剩余 3 次机会):111
密码错误,请重试。
请输入密码(剩余 2 次机会):222
密码错误,请重试。
请输入密码(剩余 1 次机会):333
已连续3次输入错误,账户已锁定。
请联系管理员:admin@feixiang.net
登录流程结束。

示例二:工资条批量生成

大翔需要按员工编号范围批量生成工资条。while 循环从起始编号逐个处理到结束编号:

/**
 * 飞翔科技工资条批量生成——while 循环(固定范围遍历)
 */
public class PayrollGeneration {
    public static void main(String[] args) {
        int startId = 1001; // 起始员工编号
        int endId = 1008;   // 结束员工编号
        int currentId = startId; // ① 初始化

        String[] names = {"大翔", "白歌", "小崔", "朱璐",
                          "Frank", "李明", "王芳", "赵磊"};
        int[] salaries = {60000, 45000, 25000, 22000,
                          30000, 18000, 16000, 20000};

        int index = 0;
        int totalPayroll = 0; // 总薪酬支出

        System.out.println("===== 飞翔科技工资条 =====");
        System.out.println("月份:2024年6月");
        System.out.println("------------------------------");

        while (currentId <= endId) { // ② 条件
            String name = names[index];
            int salary = salaries[index];
            double tax = salary * 0.1; // 简单按10%计税
            double netPay = salary - tax;

            System.out.printf("编号:%d | 姓名:%s | 应发:¥%d | "
                + "个税:¥%.0f | 实发:¥%.0f\n",
                currentId, name, salary, tax, netPay);

            totalPayroll += salary;
            currentId++; // ③ 迭代:编号 +1
            index++;     // ③ 迭代:数组索引 +1
        }

        System.out.println("------------------------------");
        System.out.println("本月薪酬总额:¥" + totalPayroll);
        System.out.println("工资条全部生成完毕。");
    }
}
运行输出:
===== 飞翔科技工资条 =====
月份:2024年6月
------------------------------
编号:1001 | 姓名:大翔 | 应发:¥60000 | 个税:¥6000 | 实发:¥54000
编号:1002 | 姓名:白歌 | 应发:¥45000 | 个税:¥4500 | 实发:¥40500
编号:1003 | 姓名:小崔 | 应发:¥25000 | 个税:¥2500 | 实发:¥22500
编号:1004 | 姓名:朱璐 | 应发:¥22000 | 个税:¥2200 | 实发:¥19800
编号:1005 | 姓名:Frank | 应发:¥30000 | 个税:¥3000 | 实发:¥27000
编号:1006 | 姓名:李明 | 应发:¥18000 | 个税:¥1800 | 实发:¥16200
编号:1007 | 姓名:王芳 | 应发:¥16000 | 个税:¥1600 | 实发:¥14400
编号:1008 | 姓名:赵磊 | 应发:¥20000 | 个税:¥2000 | 实发:¥18000
------------------------------
本月薪酬总额:¥236000
工资条全部生成完毕。

while 与 for 的本质等价

任何 while 循环都可以转换为 for 循环,反之亦然:

// while 版本
int i = 0;           // 初始化
while (i < 10) {     // 条件
    System.out.println(i);
    i++;             // 迭代
}

// 等价 for 版本
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

选择建议:当你思考问题时自然使用"当...时,重复做..."的语言,就用 while;当自然使用"从...到...,每次..."的语言,就用 for。


深度原理:while 循环的字节码实现

Java 源码:

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

反编译字节码(javap -c):

 0: iconst_0              // i = 0
 1: istore_1
 2: iload_1               // 加载 i
 3: bipush 10             // 加载常量 10
 5: if_icmpge 21          // 如果 i >= 10,跳转到偏移量 21(循环结束)
 8: getstatic  System.out    // 准备 println
11: iload_1               // 加载 i
12: invokevirtual println    // println(i)
15: iinc      1, 1         // i++
18: goto       2           // 跳回偏移量 2(条件判断)
21: return

与 for 循环字节码对比:while 和 for 在字节码层面几乎完全相同,只有两点差异:

  1. while 的初始化指令在循环判断之前(无额外标记)
  2. for 的迭代指令在 goto 之前,而 while 的迭代在循环体中由开发者显式编写

JIT 编译器进一步优化后,两者的性能完全相同。


易错场景

反例一:忘记迭代导致死循环

小崔写了一个打印员工列表的 while 循环,却忘记让索引递增:

// ❌ 错误:缺少迭代语句 → 死循环
String[] employees = {"大翔", "白歌", "小崔"};
int i = 0;
while (i < employees.length) {
    System.out.println(employees[i]);
    // 忘记 i++ → i 永远为 0,条件永远为 true → 死循环!
}
运行输出:
大翔
大翔
大翔
...(无限循环,直到强制终止)
// ✅ 正确:务必在循环体中包含迭代逻辑
String[] employees = {"大翔", "白歌", "小崔"};
int i = 0;
while (i < employees.length) {
    System.out.println(employees[i]);
    i++;  // 必须!
}

反例二:循环体可能不执行

// while 循环体可能一次都不执行
int count = 0;
while (count > 0) {   // 首次判断即为 false
    System.out.println("这行永远不会打印");
}
// 没有任何输出

如果需要"至少执行一次",请使用 do-while。

反例三:条件永远为 true 的死循环

// ❌ 错误:条件永远为 true(除非 break)
while (true) {
    System.out.println("服务器运行中...");
    // 缺少退出条件
}
// 程序永远不会正常终止
// ✅ 正确:设置明确的退出条件
boolean running = true;
int tickCount = 0;
while (running) {
    System.out.println("服务器运行中...第 " + (++tickCount) + " 秒");
    if (tickCount >= 5) {
        System.out.println("收到关闭信号,服务器即将停止。");
        running = false; // 退出循环
    }
}
运行输出:
服务器运行中...第 1 秒
服务器运行中...第 2 秒
服务器运行中...第 3 秒
服务器运行中...第 4 秒
服务器运行中...第 5 秒
收到关闭信号,服务器即将停止。

反例四:while 中的 continue 陷阱

// ❌ 错误:continue 跳过了迭代语句
int i = 0;
while (i < 5) {
    if (i == 2) {
        continue;  // 跳回条件判断,但 i 没递增 → 死循环!
    }
    System.out.println(i);
    i++;
}
运行输出:
0
1
...(之后无限循环,因为 i 始终为 2)

这是 while 和 for 的重要区别:

// for 循环中 continue 后迭代语句仍会执行
for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue;  // i++ 仍然执行,i 变为 3
    }
    System.out.println(i); // 输出 0, 1, 3, 4
}
// ✅ while 中正确使用 continue
int i = 0;
while (i < 5) {
    if (i == 2) {
        i++;        // 必须在 continue 前手动递增
        continue;
    }
    System.out.println(i);
    i++;
}

面试考点

Q1:while 循环和 for 循环在什么情况下可以互换?什么情况下不可以?

可以互换:几乎所有循环都可以互换。

推荐场景:

  • 遍历数组/集合(循环次数已知)→ for
  • 等待某个条件满足(循环次数未知)→ while
  • 至少执行一次 → do-while

代码风格:选择最清晰表达意图的结构,而不是死板地遵循规则。

Q2:while(true) 和 for(;😉 有什么区别?

语义上无区别,两者都产生无限循环。字节码层面:

  • while(true):编译器生成 goto 回路
  • for(;;):省略条件表达式,编译器同样生成无条件跳转

性能完全相同。选择哪个是代码风格问题,while(true) 更直观,for(;;) 更紧凑。

Q3:while 循环中如何安全使用 continue?

while 循环中,continue 会跳过当前迭代并直接回到条件判断。因此必须确保迭代逻辑在 continue 之前执行,否则会造成死循环。

推荐做法:将迭代操作放在循环体的最前面(或使用 for 循环避免此问题)。

Q4:while 循环的条件表达式可以写赋值语句吗?

Java 不允许——条件表达式必须返回 boolean 类型,而赋值语句返回的是被赋的值(如 (i = 5) 返回 int 类型 5)。编译器会报错:int cannot be converted to boolean。

这与 C 语言不同,C 语言中 while (i = 5) 是合法的(且极其容易引发 Bug)。Java 的类型安全设计再次保护了你。


小结

要点说明
先判断,后执行while 可能在 0 次执行后即退出
三要素分散初始化在循环前,条件在括号内,迭代在循环体中
与 for 本质等价任何 while 都可改写为 for,选择标准是语义清晰度
警惕缺少迭代循环体中必须有让条件趋向 false 的逻辑
while 中的 continue 陷阱continue 跳过迭代语句可能导致死循环
字节码与 for 几乎一致主要区别仅在于初始化指令的放置位置
上一页
switch 多分支选择
下一页
do-while 循环