一、课程概述 JavaWeb课程核心内容：Servlet、JSP和数据库连接。 课程定位： 本课程聚焦JavaWeb开发的底层技术，围绕“请求-响应”、“页面渲染”、“数据持久化”三大核心场景，系统讲解Servlet（服务端逻辑处理）、JSP（动态页面渲染）和数据库连接（数据持久化）三大技术模块。通过“理论+实验+项目实战”的模式，建立JavaWeb开发的底层认知，掌握从用户请求到数据存储的完整链路开发能力，为后续学习Spring Boot、SSM等框架奠定坚实基础。 核心内容： 课程围绕“用户发起请求 → 服务端处理（Servlet）→ 数据存储（数据库）→ 服务端处理（Servlet）→ 页面响应（JSP）”的完整Web网站开发流程展开，具体内容包括： 1.Servlet：Web应用的“控制者” Servlet核心概念：什么是Servlet？它在JavaWeb中的角色（请求处理器）；Servlet的生命周期（初始化、服务、销毁）。 请求与响应机制：HTTP请求/响应格式（GET/POST区别、请求头/参数/体解析、响应状态码）；HttpServletRequest与HttpServletResponse对象的使用。 Servlet配置与映射：传统web.xml配置与注解（@WebServlet）；多Servlet协作与请求转发（RequestDispatcher）、重定向（sendRedirect()）。 会话管理：Cookie与Session的区别与应用（用户登录状态保持、购物车功能）；Session失效机制与优化。 2.JSP：动态页面的“展示者” JSP基础语法：JSP页面结构（HTML + JSP标签 + Java代码）；脚本片段（<% %>）、表达式（<%= %>）、声明（<%! %>）的使用场景与注意事项。 JSP指令与动作：<%@ page %>（编码、导入类）、<%@ include %>（静态包含）、<jsp:include>（动态包含）；<jsp:forward> 与Servlet转发的协同。 JSTL与EL表达式：为什么需要JSTL（替代复杂Java代码）？核心标签库（c:forEach、c:if）的使用；EL表达式（${}）快速获取请求/会话/应用作用域数据。 MVC模式实践：如何在JSP中分离“视图”与“逻辑”（避免页面嵌入大量Java代码）；Servlet作为控制器、JSP作为视图的分工设计。 3.数据库连接：数据持久化的“沟通者” 数据库基础：关系型数据库（MySQL）与SQL基础（增删改查、联表查询）；JDBC核心组件（DriverManager、Connection、PreparedStatement、ResultSet）。 JDBC连接流程：加载数据库驱动 → 建立连接（DriverManager.getConnection()）→ 执行 SQL（PreparedStatement 防 SQL 注入）→ 处理结果集 → 关闭资源。 数据库连接池：为什么需要连接池（提升性能、复用连接）？常见的连接池的配置与使用。 分层设计实践：DAO（Data Access Object）模式封装数据库操作；Service层调用DAO完成业务逻辑（如用户注册时校验用户名唯一性）。 学习目标： 独立开发Web应用核心功能：能编写Servlet处理用户请求，用JSP渲染动态页面，并通过JDBC实现数据存储与查询。 理解Web开发生命周期：从HTTP请求到页面响应的完整链路（请求解析 → 业务逻辑 → 数据操作 → 视图渲染）。 解决常见开发问题：如Session失效、SQL注入防护、连接池配置异常、JSP页面数据不更新等。 完成综合项目：通过实战项目，整合Servlet、JSP、数据库技术，完成可运行的Web应用。 二、准备工作 1. 硬件与系统要求 操作系统：Windows 11+/macOS 10.15+/CentOS 7+。 硬件建议：内存 ≥8GB（Tomcat、IDE和MySQL运行需资源）、磁盘空间 ≥20GB（JDK、IDE、数据库等）。 2. 软件下载 JDK：官网链接 Java Development Kit(JDK)是 Java 编程的核心工具包，不同版本的 JDK 引入了各种新特性、性能优化和安全性增强。 以下是 JDK LTS（长期支持版本）对比： JDK 8 JDK 11 JDK 17 JDK 21 发布时间 2014-03 2018-09 2021-09 2023-09 LTS 支持截止 2030-12 2026-09 2029-09 2031-09 核心特性 - Lambda 表达式- Stream API- 新日期时间 API（java.time） - HTTP Client API- ZGC（实验性）- Flight Recorder - 密封类- 模式匹配- ZGC 生产可用 - 虚拟线程- 分代 ZGC- 记录模式匹配 性能优化 Parallel GC 为主- G1 GC 可选 G1 GC 优化- ZGC 初步成熟 ZGC 正式版- Shenandoah GC 支持 虚拟线程优化- 分代 ZGC 降低内存占用 语言特性 函数式编程基础（Lambda） var 关键字- Record 预览 密封类- 模式匹配增强 隐式类- 字符串模板 生态兼容性 绝大多数遗留系统- Spring Boot 1.x Spring Boot 2.x 兼容 Spring Boot 3.x 最低要求 Spring Boot 3.x+ 优化支持 适用场景 维护旧系统- 中小规模应用 传统企业系统过渡 稳定生产环境- 云原生基础 高并发云原生应用- 前沿技术探索 Tomcat：官网链接 推荐版本9/10.1/11，根据JDK版本选择。由于开发中通常由IDE控制Tomcat的运行状态，所以下载Tomcat的zip压缩包即可。 选择Tomcat版本：Apache Tomcat是Jakarta EE（原 Java EE）技术的开源实现，不同版本的Tomcat支持不同规范的版本。 Tomcat版本 支持的Java版本 Servlet规范 JSP规范 JSTL规范 EL规范 WebSocket规范 JASPIC规范 注解规范 最新发布版本 11.0.x 17及以上 6.0 3.1 2.0 4.0 3.1 3.0 3.0 11.0.10 10.1.x 11及以上 6.0 3.1 2.0 4.0 3.1 3.0 3.0 10.1.44 9.0.x 8及以上 4.0 2.3 1.0 3.0 2.0 2.1 2.1 9.0.108 IDE（二选一即可）： IntelliJ IDEA Ultimate：官网链接 IDEA是适用于Java和Kotlin开发的专业IDE，需要注意的是它的Community版本不支持Web应用开发； Eclipse：官网链接 使用Eclipse开发Web应用优先选用Eclipse IDE for Enterprise Java and Web Developers。 MySQL数据库：官网链接 推荐版本8.4+，长期支持版。 MySQL数据库驱动：官网链接 Connector/J 9.0实现了JDBC 4.2，支持MySQL 8.0及更高版本，支持JDK 8或更高版本。 Visual C++可再发行程序包：官网链接 Visual C++可再发行程序包安装 Microsoft C 和 C++ (MSVC) 运行时库。许多使用Microsoft C和C++工具生成的应用程序都需要这些库。 必须在安装MySQL数据库8.4之前在操作系统上安装Microsoft Visual C++可再发行程序包。可再发行程序包体系结构必须与应用的目标体系结构相匹配。 三、软件安装与配置 0.当前系统环境 本文使用64位Win11专业版操作系统作为开发环境配置演示，已安装截止目前（2025年08月23日）所有系统补丁，系统信息如下： 设备名称 HyperV-Win11 处理器 Intel(R) Xeon(R) E-2224 CPU @ 3.40GHz (3.41 GHz) 机带 RAM 16.0 GB 系统类型 64 位操作系统, 基于 x64 的处理器 注意，配置开发环境时： 操作系统的设备名称、登录使用的用户名，都不要包含中文字符！ 操作系统的设备名称、登录使用的用户名，都不要包含中文字符！ 操作系统的设备名称、登录使用的用户名，都不要包含中文字符！ 本文使用的软件版本信息如下： 软件名称 软件版本 文件名称 校验码 百度网盘 JDK 17.0.16 jdk-17.0.16_windows-x64_bin.exe sha256 网盘链接 提取码: cn6t Tomcat 11.0.10 apache-tomcat-11.0.10-windows-x64.zip sha512 网盘链接 提取码: 7g2f IntelliJ IDEA Ultimate 2025.2 ideaIU-2025.2.exe MD5 网盘链接 提取码: u3ht Eclipse 2025-06 eclipse-jee-2025-06-R-win32-x86_64.zip sha1 网盘链接 提取码: xh3s MySQL 8.4.6 mysql-8.4.6-winx64.msi MD5 网盘链接 提取码: eqmy MySQL Connector/J 9.4.0 mysql-connector-j-9.4.0.zip MD5 网盘链接 提取码: va7f Visual C++ 14.44.35211.0 VC_redist.x64.exe MD5 网盘链接 提取码: iwgn 1. JDK 安装 安装步骤：（提示：JDK的安装无需修改任何选项，一直下一步到结束。） 双击打开“jdk-17.0.16_windows-x64_bin.exe”安装文件，按照如下步骤进行安装； 点击“下一步”进入选择安装目录界面； 保持默认安装目录即可，点击“下一步”开始安装； 等待安装； 完成后，点击关闭完成JDK的安装。 验证安装： 按下“win+r”组合键，输入“cmd”回车，运行cmd.exe工具，如下： 输入命令 java -version ，正确安装将显示当前系统JDK版本信息，如下： 输入命令 javac -version，显示版本信息即成功，如下： 环境变量配置：（安装多个版本的JDK时，需通过环境变量配置指定使用的JDK版本，此处略述如下） 新建系统变量JAVA_HOME → 值为 JDK 安装路径（如 C:\Program Files\Java\jdk-17）； 编辑Path变量 → 添加 %JAVA_HOME%\bin。 2. Tomcat 安装 安装步骤：（提示：Tomcat的安装只需将压缩包内文件解压即可。） 使用文件管理器或者WinRAR等压缩软件打开下载的Tomcat压缩包“apache-tomcat-11.0.10-windows-x64.zip”； 将Tomcat压缩包中的文件夹“apache-tomcat-11.0.10”，解压到无中文路径（如 C:\apache\tomcat\）。 由于Tomcat服务器的启动、更新和关闭都可以通过IDE进行控制，至此Tomcat就算安装完毕。 Tomcat服务器的启动与关闭：（非必需操作，此处略述如下） 在Windows系统中，先完成上述省略的JDK环境变量配置操作： 启动用 Tomcat解压文件夹\bin\startup.bat；关闭用 Tomcat解压文件夹\bin\shutdown.bat。 验证启动：浏览器访问 http://localhost:8080，看到 Tomcat 欢迎页即成功。 常见问题： 端口冲突（8080 被占用）：修改 conf\server.xml 中的 <Connector port="8080"> 为其他端口（如 8081）。 内存不足：调整 Tomcat解压文件夹\bin\catalina.bat中的 JVM 内存参数（如 -Xms512m -Xmx1024m）。 3. IDE 安装与基础配置（以Eclipse为例） 安装步骤：（提示：IDE二选一，用Eclipse或者IntelliJ IDEA Ultimate任一即可，Eclipse开源免费无需激活，但为英文版且不建议进行汉化） 使用文件管理器或者WinRAR等压缩软件打开下载的eclipse压缩包“eclipse-jee-2025-06-R-win32-x86_64.zip”，如下： 将eclipse压缩包内的文件夹“eclipse”，解压到无中文路径（如 C:\），如下： 双击打开“C:\eclipse\eclipse.exe”（可发送快捷方式到桌面），如下： 第一次启动耗时较长，启动界面如下： 启动过程中需指定workspace工作目录，保持默认即可，不建议勾选“Use this as the default and do not ask again”，如下： 点击“Launch”后Eclipse继续启动，如下： Eclipse启动完成后界面如下： 基础配置步骤01-修改Eclipse使用UTF-8字符集： 点击Eclipse菜单栏Windows–>Preferences，如下： 打开的Eclipse设置界面，如下： 在Eclipse设置界面左侧选择General–>Context Types，选中“Java Class File”和“Text”，将Default encoding设置为“UTF-8”，点击“Update”，如下： 展开上一步中的Text节点，选中JSP，将Default encoding设置为“UTF-8”，点击“Update”，最后点击“Apply and Close”关闭设置界面，如下： 基础配置步骤02-在Eclipse中添加Tomcat服务器： 点击Eclipse菜单栏Windows–>Preferences，如下： 打开的Eclipse设置界面，如下： 在Eclipse设置界面左侧选择Server–>Runtime Environment，点击“Add”，如下： 选择“Apache”节点下的“Apache Tomcat v11.0”，勾选“Create a new local server”，点击“Next”，如下： 点击“Brownse”，如下： 选择之前解压至C:\apache\tomcat目录里的apache-tomcat-11.0.10文件夹，点击“选择文件夹”，如下： 确定“Tomcat installation directory”是apache-tomcat-11.0.10文件夹的路径，点击“Finish”，如下： 确定“Runtime Environment”中出现刚添加的Tomcat服务器，点击“Apply and Close”关闭设置界面，如下： 此时Eclipse软件左侧的项目管理器菜单中将会出现“Server”节点，展开后可以查看到Tomcat服务器的相关信息。此时可以点击Eclipse软件底部的Server选项卡中的“Tomcat v11.0 Server at localhost[Stopped,Republic]”，然后点击快捷按钮启动Tomcat服务器，如下： 第一次启动可能因为防火墙阻挡导致Tomcat服务器启动失败，允许后再次启动Tomcat服务器即可。Tomcat服务器启动成功后，Server选项卡中的“Tomcat v11.0 Server at localhost[Stopped,Republic]”会变为“Tomcat v11.0 Server at localhost[Started,Republic]”，如下： 关闭启动的Tomcat服务器，可以选中Server选项卡中正在运行的Tomcat服务器，然后点击快捷按钮停止Tomcat服务器，如下： 基础配置步骤03-修改Eclipse中的编译兼容级别： 点击Eclipse菜单栏Windows–>Preferences，如下： 打开的Eclipse设置界面，如下： 在Eclipse设置界面左侧选择Java–>Complier，修改“Complier compliance level”默认的“21”，改为当前系统安装的JDK版本号“17”，点击“Apply and Close”关闭设置界面，如下： 在弹出的提示框中点击“Yes”确认修改Eclipse中的编译兼容级别，如下： 4. IDE 安装与基础配置（以 IntelliJ IDEA 为例） 安装步骤：（提示：IDE二选一，用Eclipse或者IntelliJ IDEA Ultimate任一即可，IntelliJ IDEA Ultimate需要激活，但有官方中文插件） 双击打开下载的IDEA安装文件“ideaIU-2025.2.exe”，在安装界面中点击“下一步”，如下： 维持默认的安装目录即可，点击“下一步”，如下： 可以勾选创建桌面快捷方式，点击“下一步”，如下： 维持默认的选项即可，点击“安装”，如下： 等待安装完成，如下： 点击“完成”关闭安装界面，如下： 此时可以通过桌面生成的IDEA快捷方式运行IntelliJ IDEA，第一次运行可以根据区域选择相应的语言包，选好语言包后点击“下一个”，如下： 勾选“我确认我已阅读并接受此《用户协议》的条款”后点击“继续”，如下： 点击“不发送”，如下： 可以跳过导入其他软件的设置信息，如下： 第一次打开IntelliJ IDEA的界面如下： 基础配置步骤01-激活IntelliJ IDEA： 点击IntelliJ IDEA的无项目启动界面左下角的齿轮按钮，选择“管理订阅”，如下： 在“管理订阅”中选择JetBrains账户、激活码或者许可证服务器方式激活，如下： 按照国家规定，本文不涉及任何破解方式。本文采用JetBrains官方的教育授权的Ultimate订阅进行激活，激活后“管理订阅”将显示相应的激活信息，如下：（注意，不激活Ultimate订阅就无法进行Web应用开发） 基础配置步骤02-在IDEA中添加Tomcat服务器： 点击IntelliJ IDEA的无项目启动界面左下角的齿轮按钮，选择“设置”，如下： 在“管理订阅”中展开“构建、执行、部署(Build, Execution, Deployment)”节点，选中“应用程序服务器”，点击“+”号，选择“Tomcat服务器”，如下： 在弹出的“Tomcat服务器”界面点击Tomcat主目录后的“浏览”图标，如下： 选择之前解压至C:\apache\tomcat目录里的apache-tomcat-11.0.10文件夹，点击“选择文件夹”，如下： IDEA会自动识别apache-tomcat-11.0.10文件夹中Tomcat服务器的版本信息，点击“确定”，如下： 可以在设置界面查看到Tomcat的相关信息，并且默认引入的两个涉及servlet和jsp的jar文件到库中，点击“确认”关闭设置界面，如下： 5. MySQL数据库的安装 安装MySQL8.4.6LTS之前需要安装Visual C++，否则打开“mysql-8.4.6-winx64.msi”安装MySQL时，安装程序会提示缺少VC++2019导致无法安装，如下： 打开之前下载的“VC_redist.x64.exe”，勾选同意协议后，点击“安装”，如下： 等待VC++安装完毕，如下： 安转完毕后需要重启操作系统，如下： 重启操作系统后，再次打开“mysql-8.4.6-winx64.msi”安装MySQL，点击“Next”，如下： 勾选同意协议，点击“Next”，如下： 点击“Typical”，如下： Typical(典型安装)安装只安装MySQL服务器、MySQL命令行客户端和命令行实用程序。命令行客户端和实用程序包括mysqldump、myisamchk等。 Custom(定制安装)自定义安装需要的软件包。 Complete(完全安装)安装将安装软件包内包含的所有组件。完全安装软件包包括的组件包括嵌入式服务器库、基准套件、支持脚本和文档等。 点击“Install”开始安装，如下： 等待MySQL安装，如下： 安装完成后勾选“Run MySQL Configurator”，点击“Finish”，如下： 在打开的配置窗口中，点击“Next”，如下： 修改数据保存目录，维持默认即可，点击“Next”，如下： 选择数据库安装类型，维持默认即可，点击“Next”，如下： Developer Computer（开发者模式）：该选项适用于典型个人电脑。MySQL服务器将被配置成使用最少的系统资源。 Server Computer（服务器模式）：该选项适用于服务器，MySQL服务器可以协同其它应用程序一起运行。MySQL服务器将被配置成使用适当比例的系统资源。 Dedicated Computer（专用服务器模式）：该选项表示该服务器只运行MySQL服务，假定运行没有运行其它应用程序。MySQL服务器将被配置成使用所有可用系统资源。 设置MySQL的root用户密码，输入完成后，点击“Next”，如下： 设置Windows服务相关信息，维持默认即可，点击“Next”，如下： 设置MySQL服务器文件的权限，维持默认即可，点击“Next”，如下： 设置示例数据库，可以勾选安装“Sakila”和“World”两个数据库，点击“Next”，如下： 点击“Excute”开始配置MySQL数据库，如下： 等待配置完成，如下： 配置完成后，点击“Next”，如下： 点击“Finish”关闭配置窗口，如下： 四、JavaWeb 项目创建与运行 1. 创建第一个 JavaWeb 项目（以Eclipse为例） 双击“C:\eclipseeclipse.exe"，或将其发送到桌面快捷方式后双击快捷方式，启动Eclipse，界面如下： 启动过程中需指定workspace工作目录，保持默认即可，如下： 点击“Launch”后Eclipse继续启动，如下： 之前已经在Eclipse中配置了Tomcat服务器，Eclipse启动完成后界面左侧项目管理器中可以看到Server节点，如下： 点击Eclipse菜单栏File–>New–>Project，如下： 在Web节点内选择“Dynamic Web Project”，点击“Next”，如下： 输入Project name，此处示例填写“JavaWeb”，确定Target Runtime为之前配置的Apache Tomcat v11.0，点击“Next”，如下： 查看Java源码构建路径即可，后期Java相关的源码都在该路径下创建，无需修改任何内容，点击“Next”，如下： 查看站点根目录路径即可，后期JSP、JS、CSS或前端框架都在该路径下创建，无需修改任何内容，点击“Finish”完成JavaWeb项目的创建，如下： 创建好JavaWeb项目后可以在Eclipse左侧项目管理器中展开新建JavaWeb项目节点，src/main/java用于创建java文件，包括包、类、接口以及servlet、listener、filter等；src/main/webapp用于创建jsp、html、js、css以及网页页面中使用的框架、图片等，至此JavaWeb项目创建完毕，如下： 2. 运行第一个 JavaWeb 项目（以Eclipse为例） 在Eclipse左侧项目管理器中展开JavaWeb项目节点，在src/main/webapp上鼠标右键，选择“New-JSP File”，如下： 在弹出的新建JSP文件窗口中修改文件名File Name为“index.jsp”，点击“Next”，如下： 勾选“Use JSP Template”使用HTML5模板创建JSP文件，点击“Finish”，如下： 在代码窗口显示的index.jsp文件的body标签中写入代码，此处示例写入“out.print("Hello world from JSP文件");”，注意用“<%”和“%>”包裹这行代码，点击保存按钮，再点击运行按钮，代码和界面如下： <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> Insert title here <% out.print("Hello World From JSP文件。"); %> 选中之前在Eclipse中配置好的“Tomcat v11.0 Server at localhost”，点击“Next”，一定要先选中Tomcat节点将其变为蓝色底色，否则下一步不会出现JavaWeb项目，如下： 确保JavaWeb项目在右侧，如果不在，可以在左侧选中JavaWeb项目后点击“Add”添加到右侧，最后点击“Finish”，如下： Eclipse会自动启动Tomcat服务器，将JSP转换输出到网页，操作系统会自动弹出浏览器访问该JavaWeb网站并显示该网页的内容，如下： 返回Eclipse软件，在src/main/java节点上鼠标右键，选择“New-Servlet”，如下： 在包名Java Package处输入“controller”，在类名Class name输入“HelloServlet”，注意大小写，点击“Next”，如下： 此处主要用于设置初始化参数和URL映射的mapping，维持默认即可，点击“Next”，如下： 此处主要用于添加servlet需要实现的接口和生成的成员方法，维持默认即可，确保doGet和doPost方法被勾选，点击“Finish”，如下： 在Eclipse中打开的名为“HelloServlet.java”的Servlet文件中，通过import语句导入Java.io.PrintWrite，保留类名前生成的注解“@WebServlet("/HelloServlet")”，并更改doGet方法中的代码，点击保存按钮，代码和界面如下： // 默认生成的import略 import java.io.PrintWriter;// 导入Java.io.PrintWrite @WebServlet("/HelloServlet") // 保留类名前生成的注解 public class HelloServlet extends HttpServlet { // 默认生成的其他成员变量和成员方法略 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 更改doGet方法中的代码 response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("Hello World From Servlet文件。"); } } 点击保存按钮后再次点击运行按钮，如下： 按照之前运行jsp文件的方式选择Tomcat服务器重新编译运行web项目，成功后在弹出的浏览器中即可查看Servlet文件执行的结果，如下： 3. 创建第一个 JavaWeb 项目（以IDEA为例） 双击安装IntelliJ IDEA后桌面生成的IDEA图标打开IDEA，当前系统中无IDEA创建的项目，IDEA界面如下： 点击“新建项目”按钮，在弹出的“新建项目”对话框中选择新建Java项目，输入名称“JavaWeb”，构建系统先选择“IntelliJ”，检查JDK是否为之前安装的JDK版本，确认无误后点击“创建”按钮，如下： 在打开的IEAD界面左侧找到新建的JavaWeb项目节点，选中后点击快捷键F4，或在JavaWeb项目节点上鼠标右键，选择“打开模块设置”，如下： 在打开的“项目结构”窗口，左侧选择“模块”后，点击中间顶上的“+”号，选择“Web”，如下： 此时JavaWeb项目下出现Web模块，如下： 在“项目结构”窗口左侧选择“工件”，点击中间顶上的“+”号，点击“Web应用程序：展开型”，选择“源模块”，如下： 在打开的“选择模块”对话框中选中当前项目JavaWeb，点击“确定”，如下： 在“项目结构”窗口左侧选择“库”，点击中间顶上的“+”号，选择“Java”，如下： 在弹出的“选择库文件”对话框中，定位到之前解压的Tomcat文件目录中的lib文件夹，选中其中的“servlet-api.jar”文件，点击“打开”，如下： 在弹出的“选择模块对话框中”，选中当前的JavaWeb项目，点击“确定”，如下： 最后确定“项目结构”中，“模块”、“库”和“工件”都修改完成，点击“确认”按钮关闭“项目结构”界面，如下： 4. 运行第一个 JavaWeb 项目（以IDEA为例） 重回IDEA界面后可以在左侧项目管理器中发现JavaWeb项目节点下多出了“web”节点，该节点作为站点根目录用于创建jsp、html、js、css以及网页页面中使用的框架、图片等，此时项目还需要配置运行方式，点击IDEA顶部运行按钮左侧的“当前文件”，选择“编辑配置”，如下： 在打开的“运行/调试配置”窗口，点击左侧顶部的“+”号，在弹出的菜单中展开“Tomcat服务器”节点，选择“本地”，如下： 点击“修复”后点击“确认”，关闭“运行/调试配置”窗口，如下： 此时在IDEA界面顶部，运行按钮左侧变为“Tomcat”。再次找到JavaWeb项目节点，选中后点击快捷键F4，或在JavaWeb项目节点上鼠标右键，选择“打开模块设置”，如下： 在打开的“项目结构”窗口，左侧选择“模块”后，点击中间“JavaWeb”节点，点击右侧的“依赖”选项卡，点击下面的“+”号，选择“库”，如下： 在弹出的“选择库”窗口中，选中刚才配置的Tomcat服务器，点击“添加所选项”，如下： 点击“确认”按钮关闭“项目结构”界面，如下： 在“web”节点上鼠标右键，选择“新建”，点击“JSP/JSPX 页面”，如下： 输入文件名“index”，文件类型默认JSP文件即可，回车确认，如下： 在代码窗口显示的index.jsp文件的body标签中写入代码，此处示例写入“out.print("Hello world from JSP文件 in IDEA。");”，注意用“<%”和“%>”包裹这行代码，代码写好后保存，点击顶部“Tomcat”旁的运行按钮，代码和界面如下： <%@ page contentType="text/html;charset=UTF-8" language="java" %> Title <% out.print("Hello world from JSP文件 in IDEA。"); %> Tomcat服务器启动完成后会自动打开浏览器访问相应的资源将其显示出来，如下： 在JavaWeb项目的“src”节点上鼠标右键，点击“新建”，选择“软件包”，如下： 在弹出的“新建软件包”对话框中输入包名“controller”，回车确认，如下： 在刚才新建的“controller”包上鼠标右键，点击“新建”，选择“Java 类”，如下： 在弹出的“新建Java类 / 文件”对话框中选择“类”，输入类名“HelloServlet”后，回车确认，如下： 在打开的“HelloServlet.java”文件中，类名之后添加“extends HttpServlet”，如下： 将光标定位到HelloServlet类中后，通过鼠标右键-生成，或者Alt+Insert快捷键打开生成菜单，选择“重写方法”，如下： 在弹出的“选择覆写/实现的方法”对话框中，选中“doGet()”方法，点击“确定”，如下： 在生成的“doGet()”方法中输入代码，代码和界面如下： package controller; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 更改doGet方法中的代码 resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("Hello World From Servlet文件 in IDEA。"); } } 打开JavaWeb项目中的“web/WEB-INF/web.xml”文件，在添加Servlet配置信息，代码和界面如下： <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>controller.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/helloservlet</url-pattern> </servlet-mapping> </web-app> 保存所有文件，点击IDEA顶部“Tomcat”旁的重启按钮，如下： 在弹出的对话框中选择重启服务器，点击“确定”，如下： 等待Tomcat服务器重启完毕，在弹出的浏览器的地址栏中添加“/helloservlet”，回车后即可访问Serlver，如下：
一、引言 Java程序流程控制是构建程序逻辑的核心机制，包含顺序、分支、循环三大基础结构。 顺序结构是默认执行方式，代码按书写顺序逐行运行，为复杂逻辑提供基础框架。 分支结构通过条件判断实现路径选择，包括单分支（if）、双分支（if-else）及多分支（if else if ...或switch-case）。其中，switch-case语句支持等值匹配。 循环结构则通过for、while、do-while实现重复操作：for适用于已知次数的遍历，while先判断后执行，而do-while确保至少执行一次循环体。 Java 14引入的箭头语法（case A ->），既优化了代码可读性，也避免了传统case穿透问题。 此外，流程控制中还可以配合跳转语句（如break跳出循环、continue跳过当前迭代、return终止方法）增强灵活性。 使用Java程序流程控制构建高效、易维护的逻辑，以满足从简单数据判断到复杂算法实现的多样化需求。 二、流程控制的基础结构 1. 顺序结构 Java程序中的顺序结构是流程控制中最基础且不可或缺的逻辑框架，其核心特征在于代码按照书写顺序逐行执行，形成线性且不可逆的执行路径。顺序结构具有以下特征： ​线性执行逻辑​ 程序从入口点（如main方法）开始，逐行向下执行每一条语句，直到所有代码执行完毕或遇到程序终止指令。 ​无条件执行​ 顺序结构中的所有语句均会被执行，不受任何条件限制。即使后续代码中存在分支或循环，整体流程仍以顺序结构为框架。 ​代码可预测性​ 由于执行路径固定，顺序结构的程序行为预测，便于调试和维护。 下面的示例展示了Java程序中的顺序结构。 public class SequentialStructure { public static void main(String[] args) { int a = 10; // 声明变量a并赋值 int b = 20; // 声明变量b并赋值 int sum = a + b; // 计算a+b的和，并存储结果 System.out.println(sum); // 输出结果 } } 程序执行后的结果如下： 30 上面的程序严格按照声明并赋值→计算→输出的顺序执行，直到程序运行结束，体现了典型的顺序结构。 对于无需条件判断或循环的算法（如数学公式计算），顺序结构可直接实现。在用户交互场景中，顺序结构也常用于依次接收输入、处理数据并返回结果。即使程序中包含分支结构或循环结构，程序的整体流程仍以顺序结构为主体。 2. 分支结构 在Java编程中，选择结构是控制程序执行流程的重要手段，它允许程序根据条件选择不同的执行路径。Java提供了几种主要的选择结构：包括单分支（if）、双分支（if-else）及多分支（if else if ...或switch-case）。 2.1 if选择语句 if选择语句是最基本的程序流程控制语句，用于根据条件表达式的结果来决定是否执行特定的代码块。语法如下所示： if(条件表达式){ statement } 其中条件表达式必须返回一个具体的布尔值，如果返回true值，则执行{statement}中的代码；如果返回false，则结束if语句，执行{statement}之后的代码。 虽然Java语法允许在{statement}中的代码仅有一行时，可以省略大括号{}，但通常建议不要省略，以避免逻辑错误。 下面的示例展示了if语句的用法。 public class IfStatement { public static void main(String[] args) { int a = 0, b = 0; if (a == b) { System.out.println("a和b的值相等。"); } System.out.println("if语句执行完毕。"); } } 程序执行后的结果如下： a和b的值相等。 if语句执行完毕。 上面的程序首先声明a、b两个变量并赋值0，然后通过if语句判断a == b的返回值，当返回值为true时，执行if之后{}内的语句。最后执行if语句之后的语句。 2.2 if…else…选择语句 if…else…选择语句在if选择语句的基础上增加了else子句，用于根据条件表达式的结果来决定是否执行特定的代码块。语法如下所示： if(条件表达式){ statement-1 } else{ statement-2 } 其中条件表达式必须返回一个具体的布尔值，如果返回true值，则执行{statement-1}中的代码；如果返回false，则执行{statement-2}中的代码。 下面的示例展示了if…else…语句的用法。 public class IfElseStatement { public static void main(String[] args) { int a = 10, b = 20; if (a > b) { System.out.println("a和b相比较，a的值较大。"); } else { System.out.println("a和b相比较，a的值较小。"); } System.out.println("if…else…语句执行完毕。"); } } 程序执行后的结果如下： a和b相比较，a的值较小。 if…else…语句执行完毕。 上面的程序首先声明a、b两个变量并分别赋值10、20，然后通过if语句判断a > b的返回值，当返回值为true时，执行if之后{}内的语句；当返回值为false时，执行else之后{}内的语句。最后执行if…else…语句之后的语句。 2.3 if…else if…else…选择语句 if…else if…else…选择语句实际上是一种if…else…选择语句的嵌套形式，它将if…else…选择语句嵌套到上一层if…else…选择语句的else子句中，形成一种多分支的选择结构。语法如下所示： if(条件表达式-1){ statement-1 } else if(条件表达式-2){ statement-2 } else if(条件表达式-3){ statement-3 } ……//省略若干分支条件 else { statement-n } if…else if…else…选择语句从上到下执行，依次判断每个if的条件表达式，当某个条件表达式为true时，就执行与之关联的if之后{}内的语句，并略过剩余的语句执行if…else if…else…选择语句之后的语句。如果所有的条件表达式都不成立，就执行最后的else语句。 因为if语句可以独立执行，如果最后嵌套的if…else…选择语句省略了else子句，那么整个if…else if…else…选择语句的条件表达式不成立时，就直接跳过，执行其后的语句。 下面的示例展示了if…else if…else…语句的用法。 public class NestedIfElseStatement { public static void main(String[] args) { int month = 5; if (month == 12 || month == 1 || month == 2) { System.out.println("寒冷的冬天"); } else if (month >= 3 && month <= 5) { System.out.println("温暖的春天"); } else if (month == 6 || month == 7 || month == 8) { System.out.println("炎热的夏天"); } else if (month >= 9 && month <= 11) { System.out.println("凉爽的秋天"); } else { System.out.println("无效的月份"); } } } 程序执行后的结果如下： 温暖的春天 上面的程序使用if…else if…else…选择语句，在多个条件表达式中对month变量的数据进行判断，根据month变量保存的数据情况确定需要执行的语句。在这段代码中，不管month保存了哪个int取值范围内的数值，都会有一条与其条件对应的语句执行。 2.4 switch-case选择语句 switch-case选择语句是Java提供的另一种多分支选择结构的语句，它根据表达式计算结果的定值执行不同的分支语句，形成一种多分支的选择结构。语法如下所示： switch(表达式){ case value1 : statement-1 break; case value2 : statement-2 break; ……//省略若干分支条件 default : //可选 statement-n } switch后面的圆括号中的表达式必须能计算出结果，在JDK 1.7以前的版本中，该结果的数据类型只能是byte、short、int、char或枚举，从JDK 1.7开始增加支持String类型。switch后面{}中包含若干个case分支，每个case后面的都有一个常量或者字面量的定值，该定值用于和表达式的计算结果比较，如果两个值相同，就执行该值冒号之后的Java语句，直到遇到break跳出整个switch。在所有case分支之后还有一个可选的default分支，用于处理所有 case均不匹配的情况。 注意：case分支内的break可以省略，省略break会发生case穿透，即执行后续case冒号之后的Java语句直至遇到break或switch结构结束。 下面的示例展示了switch-case选择语句的用法。 public class SwitchStatement { public static void main(String[] args) { int month = 5; switch (month) { case 1: System.out.println("寒冷的冬天"); break; case 2: System.out.println("寒冷的冬天"); break; case 3: System.out.println("温暖的春天"); break; case 4: System.out.println("温暖的春天"); break; case 5: System.out.println("温暖的春天"); break; case 6: System.out.println("炎热的夏天"); break; case 7: System.out.println("炎热的夏天"); break; case 8: System.out.println("炎热的夏天"); break; case 9: System.out.println("凉爽的秋天"); break; case 10: System.out.println("凉爽的秋天"); break; case 11: System.out.println("凉爽的秋天"); break; case 12: System.out.println("寒冷的冬天"); break; default: System.out.println("输入错误"); } } } 程序执行后的结果如下： 温暖的春天 上面的程序使用switch-case选择语句，在多个case之后使用1-12的整型数据匹配month变量中保存的整型数据，当case匹配到5时，执行冒号之后的语句输出温暖的春天，然后执行break跳出switch-case选择语句。由于一年有12个月份，所以上面的switch-case选择语句使用了12个case，再加上最后的default，使得代码冗长，降低阅读性。一年的12个月份对应4个季节，利用case穿透机制可以将上面的程序代码优化为： public class SwitchStatementCaseThought { public static void main(String[] args) { int month = 5; switch (month) { case 1: case 2: case 12: System.out.println("寒冷的冬天"); break; case 3: case 4: case 5: System.out.println("温暖的春天"); break; case 6: case 7: case 8: System.out.println("炎热的夏天"); break; case 9: case 10: case 11: System.out.println("凉爽的秋天"); break; default: System.out.println("输入错误"); } } } 程序执行后的结果如下： 温暖的春天 3. 循环结构 Java的循环结构主要包括while语句、do…while语句和for语句，其中for语句包含两种形式，传统形式和for-each增强形式。循环结构会重复执行同样的语句直到根据循环条件判断应该结束循环。 一个完整的循环结构，都应该包含循环的初始状态、循环的结束状态和每次循环之后状态的改变，这三个要素会控制整个循环的执行。通常为了避免出现死循环，在设计循环结构的语句时，应该在执行循环前指定循环的初始状态，每一次循环之后状态的改变都应向着循环的结束状态靠近。 3.1 while循环语句 while循环是Java中最基础的循环语句，是一种条件驱动的循环结构，只要条件表达式返回的结果为true，它就会一直执行循环体中的代码。语法如下所示： while(条件表达式){ 循环体 } while循环语句中，while关键字后括号内容的条件表达式会返回一个布尔值，只要该布尔值为true，就会执行循环体中的语句，当返回的布尔值为false时，就会跳过循环体，执行while循环之后的语句。如果while循环语句的循环体只有一行语句，就可以省略包裹循环体的花括号，但通常不建议这样做。此外，while循环语句还可以省略循环体，省略循环体时，需要直接在包含条件表达式的圆括号之后使用分号结尾。 下面的示例展示了while循环语句的用法。 public class WhileStatement { public static void main(String[] args) { int i = 5;// 循环的初始状态 while (i > 0) {// 循环的终止状态 System.out.println("倒计时：" + i); i--;// 循环状态的改变 } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序使用整型变量i值控制while循环，在进入while循环语句之前，赋值i值为10作为while循环的初始状态。然后执行while循环时，在条件表达式中判断i值是否满足循环的条件i>0，满足的话就执行循环体中的输出语句并将变量i自减，不满足的话就终止执行while循环语句。 下面的示例展示了while循环语句不带循环体的用法。 public class WhileStatementWithNobody { public static void main(String[] args) { int i = 0;// 循环的初始状态 int j = 100;// 循环的初始状态 while (++i < --j)// 循环的终止状态和循环状态的改变 ; System.out.println("while循环结束后，i = " + i + "，j = " + j + "。"); } } 程序执行后的结果如下： while循环结束后，i = 50，j = 50。 上面的程序使用了一个不包含循环体的while循环语句，在while循环之前赋值i值为0，j值为100作为循环的初始状态，然后在while循环的条件表达式中，完成循环状态的改变和循环终止状态的判断。所以，即使这个while循环语句没有循环体，依然能够正常结束循环。 3.2 do…while循环语句 do…while循环语句在结构上类似while循环语句的倒装，do…while循环语句会先执行循环体中的语句，再根据控制循环的条件表达式的返回值决定是否继续循环的执行。语法如下所示： do{ 循环体 }while(条件表达式); do…while循环语句中，循环体语句在do之后的花括号中，花括号之后才是while(条件表达式)，所以从结构上看，程序在进行循环条件判断之前，就已经执行了循环体语句。这里需要注意的是，while(条件表达式)之后的英文分号不能省略。 下面的示例展示了do…while循环语句的用法。 public class DoWhileStatement { public static void main(String[] args) { int i = 5;// 循环的初始状态 do { System.out.println("倒计时：" + i); i--;// 循环状态的改变 } while (i > 0);// 循环的终止状态 } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序将之前使用while循环语句的倒计时改写为do…while循环语句，虽然从执行输出的结果上看，两种循环语句是一样的，但在循环执行的顺序上，一定要注意第一句倒计时：10，while循环是判断了条件表达式之后输出的，而do…while循环是先输出了一次再判断条件表达式。上面的示例还可以使用类似不带循环体的while循环中的简化方式对代码进行优化，优化后的示例如下： public class DoWhileStatement { public static void main(String[] args) { int i = 5;// 循环的初始状态 do { System.out.println("倒计时：" + i); } while (--i > 0);// 循环状态的改变和循环的终止状态 } } 3.3 for循环语句 Java中for循环是最常用的循环语句，它有两种不同的形式。第一种是传统形式，它将循环的三个要素统一放置在for后面的括号中，适用于循环次数固定的场景；第二种是增强形式，它自JDK5开始引入，主要用于循环遍历数组或者集合。 3.3.1 for循环语句的传统形式 for循环的传统形式语法如下所示： for (初始化表达式; 循环条件; 迭代表达式) { // 循环体 } 如果循环体仅有一条语句，从语法上可以像while循环一样省略循环体外侧的花括号，但通常不建议这样。 for循环的执行过程如下： 第一步：在for循环开始前执行一次初始化表达式​，初始化表达式需要声明或初始化循环控制变量（如int i = 0）。必须注意，初始化表达式仅执行一次，它对应循环的初始状态。 第二步：对​循环条件进行求值，循环条件通常是一个布尔表达式（如i < 5）。若循环条件为true则进入第三步执行循环体，否则退出循环，循环结束，它对应循环的终止状态。 第三步：若第二步中循环条件为true，则执行循环体。 第四步：执行​迭代表达式（如i++）​，确保每次循环体执行后更新循环变量，然后从第二步开始重复循环，它对应循环状态的改变。 下面的示例展示了for循环语句传统形式的用法。 public class ForStatement { public static void main(String[] args) { for (int i = 5; i > 0; i--) { System.out.println("倒计时：" + i); } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序依然实现的是一个倒计时，但对比代码，可以看到对于确定循环次数的场景，for循环语句的代码更加简洁。 3.3.2 for循环的传统形式的变体 在for循环外访问循环控制变量 在前面的for循环语句传统形式的用法的示例中，在for循环结束之后，循环控制变量i就被销毁了。 for (int i = 5; i > 0; i--) { System.out.println("倒计时：" + i); } System.out.println("for循环结束后访问i值：" + i); 此时运行程序会引发错误：i cannot be resolved to a variable。这是因为在for循环的初始化表达式中声明的变量，其作用域仅限于for循环语句中。如果需要在for循环外访问循环控制变量，可以将初始化表达式中循环控制变量的声明改写在for循环之前。 下面的示例展示了在for循环外访问循环控制变量的用法。 public class ForStatementVariant1 { public static void main(String[] args) { int i;// 循环控制变量的声明在for循环之前 for (i = 5; i > 0; i--) {// for循环的初始化表达式仅对循环控制变量的赋值 System.out.println("倒计时：" + i); } System.out.println("for循环结束之后的i值：" + i); } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 for循环结束之后的i值：0 上面的程序将循环控制变量i的作用域扩展到了整个main()方法，所以在for循环结束之后仍能访问其值。 使用逗号增加循环控制条件 for循环的传统形式还有一种特殊的用法，它允许使用多个循环控制变量控制一个for循环。要实现这种控制方式，可以在for循环的初始化表达式和迭代表达式中包含多条语句，多条语句之间使用逗号分开。 下面的示例展示了在for循环中使用逗号增加循环控制条件的用法。 public class ForStatementVariant2 { public static void main(String[] args) { int i, j; for (i = 1, j = 4; i < j; i++, j--) { System.out.println("i = " + i); System.out.println("j = " + j); } } } 程序执行后的结果如下： i = 1 j = 4 i = 2 j = 3 上面的程序使用i和j两个循环控制变量控制循环，这种变体适用于需要多个循环控制变量的场景。 可以省略的初始化表达式、循环条件或迭代表达式 for循环中圆括号里的初始化表达式、循环条件和迭代表达式可以选择性的省略，但它们之间的分号不能省略。 下面的示例展示了在for循环中省略初始化表达式和迭代表达式的用法。 public class ForStatementVariant3 { public static void main(String[] args) { int i = 5;// 循环控制变量的声明和初始化 for (; i > 0;) {// 省略初始化表达式和迭代表达式 System.out.println("倒计时：" + i); i--;// 迭代表达式在这里 } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序在for循环中圆括号里省略了省略初始化表达式和迭代表达式，但其实初始化表达式和迭代表达式仍在程序中，只是移动了位置。初始化表达式写在for循环语句之前，迭代表达式被放到了循环体语句中。由于初始化表达式在for循环的执行步骤中只在第一步执行一次，所以它被移动到for循环之前并不影响循环的执行步骤。但迭代表达式移动到循环体中以后，就可以改变原来for循环的执行步骤，这也是这种变体的主要适用场景。 for循环圆括号中的循环条件也可以省略，虽然单独的for(;;){}在语法上没有问题，但它就变成了一个无限循环。所以省略了循环条件的for循环，需要在循环体内配合跳转语句结束循环。 下面的示例展示了在for循环中省略初始化表达式和迭代表达式的用法。 public class ForStatementVariant4 { public static void main(String[] args) { int i = 5;// 循环控制变量的声明和初始化 for (;;) {// 省略初始化表达式、循环条件和迭代表达式 System.out.println("倒计时：" + i); i--;// 迭代表达式在这里 if (i < 1) { break;// 循环条件在这里 } } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序在for循环中圆括号里省略了省略初始化表达式、循环条件和迭代表达式，类似之前的变体，其实只是把循环条件改成一个具体的if语句进行条件判断，如果条件为真，就执行if语句中的break跳出循环。由于if语句在循环体中的位置可以改变，所以这种变体的适用场景也是针对需要改变for循环执行步骤的特殊实现。 3.3.3 for循环语句的增强形式 for循环的增强形式自JDK5开始引入，它无需传统形式中的初始化表达式、循环条件和迭代表达式，就能以严格的顺序形式，从头到尾循环遍历一个数组或者一个实现了Iterable接口的集合对象中的每一个元素。 for循环的增强形式语法如下所示： for (数据类型 迭代变量 : 数组或集合) { // 循环体中使用迭代变量访问每一个数据或集合的元素 } for循环的增强形式中的圆括号里，分号将其中的内容分为两个部分，第一部分通过数据类型 迭代变量的形式，声明一个迭代变量，用于在每次循环时按顺序保存数组或集合中的一个未遍历过的元素；第二部分则指定一个需要循环遍历的数组或集合。 注意：由于迭代变量中保存的数据都来自于数据或者集合，所以要求数组或者集合中的元素的数据必须一致，对于数组来说，通常数组元素的数据类型都是相同的，无需多虑，但对于集合来说，由于add()方法接受的参数可能是Object类型，这就需要在集合对象中使用泛型对其中的元素限定统一的数据类型。 下面的示例展示了for循环语句增强形式的用法。 public class ForEachStatement { public static void main(String[] args) { int[] countdown = { 5, 4, 3, 2, 1 }; for (int num : countdown) { System.out.println("倒计时：" + num); } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序通过for循环的增强形式从头到尾读取了int数组countdown中的每一个元素。但需要注意的是，在for循环的增强形式中，迭代变量是只读的，即只能通过迭代变量读取数据，而不能通过迭代变量写入数据。 下面的示例展示了for循环语句增强形式中使用迭代变量写入数据的错误用法。 public class ForEachStatementWrongWay { public static void main(String[] args) { int[] countdown = { 5, 4, 3, 2, 1 }; for (int num : countdown) { num = num * 100; } for (int num : countdown) { System.out.println("倒计时：" + num); } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序使用了两个for循环的增强形式访问int数组countdown中的每一个元素，第一个for循环使用迭代变量将其中保存的数据乘以100，第二个for循环使用迭代变量重新读取int数组countdown中的每一个元素，从执行后的结果来看，int数组countdown中的每一个元素并未改变其中保存的原有数据。如果在实际场景中需要对数组或者集合中的元素进行写入操作，那么只能使用for循环的传统形式。 下面的示例展示了for循环语句增强形式中使用迭代变量写入数据的错误用法。 public class ForEachStatementRightWay { public static void main(String[] args) { int[] countdown = { 5, 4, 3, 2, 1 }; int size = countdown.length; for (int i = 0; i < size; i++) { countdown[i] = countdown[i] * 100; } for (int num : countdown) { System.out.println("倒计时：" + num); } } } 程序执行后的结果如下： 倒计时：500 倒计时：400 倒计时：300 倒计时：200 倒计时：100 上面的程序先通过for循环的传统形式，利用循环控制变量作为int数组countdown数组元素的索引，改变数组中每一个元素的数据，然后通过for循环的增强形式读取int数组countdown中的每一个元素，从执行后的结果来看，int数组countdown中的每一个元素都改变为新的数据。 注意：使用for循环的传统形式以循环控制变量作为索引的形式访问数组或者集合时，一定要避免索引越界引发ArrayIndexOutOfBoundsException异常。 3.3.4 for循环语句的局部变量类型推断 局部变量类型推断是Java 10引入的重要特性（JEP 286），通过var关键字让编译器自动推断变量的数据类型。局部变量类型推断可以使得for循环显著简化代码。使用局部变量类型推断时，需要在声明变量时使用var关键字作为变量的数据类型，而且必须在声明变量的同时进行赋值。 下面的示例展示了for循环语句中使用局部变量类型推断的用法。 public class TypeInference { public static void main(String[] args) { for (var i = 5; i > 0; i--) {// 等价于for (int i = 5; i > 0; i--) System.out.println("倒计时：" + i); } int[] countdown = { 5, 4, 3, 2, 1 }; for (var num : countdown) {// 等价于for (int num : countdown) System.out.println("倒计时：" + num); } } } 程序执行后的结果如下： 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 倒计时：5 倒计时：4 倒计时：3 倒计时：2 倒计时：1 上面的程序在两种形式的for循环中，在初始化循环控制变量和迭代变量使用var关键字作为它们的数据类型，这样编译器就可以通过保存在其中的具体数据，来推断该变量的数据类型。示例中对基本数据类型进行推断并不能体现局部变量类型推断的优势，在精简代码方面，针对集合泛型的推断更具优势。 4. 跳转语句 在Java中，跳转语句用于控制程序的执行流程，主要包括三种：break语句、continue语句和return语句。 4.1 break语句 Java种break语句主要有三种用法： switch-case选择语句：终止某个case并跳出switch-case选择语句； 循环语句：终止当前循环 代替goto：配合标签实现goto操作 之前介绍switch-case选择语句已经演示了break语句的第一种用法，在此介绍后面两种用法。 4.1.1 循环语句中的break语句 在循环语句种使用break语句可以强制终止当前循环，跳过循环体剩余的代码，执行循环语句之后的代码。 下面的示例展示了循环语句中使用break语句的用法。 public class BreakStatement { public static void main(String[] args) { int i = 0; while (++i > 0) { if (i % 6 == 0 && i % 8 == 0) { break; } } System.out.println("6和8的最小公倍数是" + i); } } 程序执行后的结果如下： 6和8的最小公倍数是24 上面的程序中声明了一个int类型变量i并赋值为0，由于++i始终大于0，这里的while循环实际上是一个无限循环。每次循环都将i值逐次自增，当i值与6和8求模操作结果都为0时，就执行break语句跳出while循环，执行while循环语句之后的输出语句显示6和8的最小公倍数的计算结果。 需要注意的是，对于嵌套的循环语句，当break出现在内层循环时，它只能跳出内层循环而无法跳出外层循环。 下面的示例展示了嵌套循环语句的用法。 public class NestedLoop { public static void main(String[] args) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { System.out.print(j + "×" + i + "=" + (j * i) + " "); } System.out.println(); } } } 程序执行后的结果如下： 1×1=1 1×2=2 2×2=4 1×3=3 2×3=6 3×3=9 1×4=4 2×4=8 3×4=12 4×4=16 1×5=5 2×5=10 3×5=15 4×5=20 5×5=25 1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36 1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49 1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64 1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81 上面的程序使用两层for循环进行嵌套，外层for循环控制行，内层for循环控制行内的乘法算式，输出一个九九乘法算式表。如果在内层for循环if语句，当两个乘数相同时执行break语句，这个break不会跳出外层的for循环。 下面的示例展示了嵌套循环语句中内层执行break语句的用法。 public class NestedLoop { public static void main(String[] args) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { if(i == j) break;// 内层break不会跳出外层循环 System.out.print(j + "×" + i + "=" + (j * i) + " "); } System.out.println(); } } } 程序执行后的结果如下： 1×2=2 1×3=3 2×3=6 1×4=4 2×4=8 3×4=12 1×5=5 2×5=10 3×5=15 4×5=20 1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 上面的程序在内层循环发现两层循环控制变量相等时if(i == j)，执行了break语句，但是跳出内层循环之后，返回到了外层循环。可以看出，内层执行的break语句对外层循环没有影响。如果在多层嵌套循环中需要跳出循环到指定位置，只能使用break语句的第三种用法——代替goto。 注意，break语句并不是停止循环语句执行的正常手段，循环语句中的循环条件才是停止循环执行的正常手段，break语句用于跳出循环仅在某些特殊场景中应用。 4.1.2 使用break语句代替goto语句 在Java中，goto是一个保留但未实现的关键字，它存在于Java的关键字列表中，这样可以避免goto被用作标识符，但它又未实现，所以不能在编程中使用它进行无条件地转移。 为什么Java不实现goto？ Java设计者做出这个决定主要基于以下原因： 代码可读性问题​：goto可能导致"面条代码"，难以跟踪执行流程； ​维护困难​：使用goto的代码难以调试和修改； 结构化编程原则​：编程语言应该更加强调使用循环、条件和方法等结构控制流程； 替代方案充足​：Java的break、continue和return等语句已经提供了足够的流程控制 为了代替goto，Java提供了break语句的一种扩展形式，通过使用这种带有标签的break语句，可以中断一个或多个任意代码块，这些代码块不局限于switch-case或者循环语句。 带有标签的break语句的扩展形式一般如下所示： break 标签名; 标签名主要用于标识一个代码块。这个被标识的代码块可以是独立的，也可作为另一条语句的目标。当break 标签名;被执行时，程序的执行控制会跳出由标签名指定的代码块。 为代码块指定标签名时，直接在代码块前放置一个标签，标签之后用冒号与代码块相邻，标签可以是任意合法的Java标识符。 注意，具有标签名的代码块中，必须包含break 标签名;语句。 下面的示例展示了break语句这种扩展形式的用法。 public class BreakLabelStatement { public static void main(String[] args) { boolean flag = true; first: { second: { third: { System.out.println("这是在执行break second语句之前的输出。"); if (flag) break second; System.out.println("这是在执行break second语句之后的不会执行的输出。"); } // third标识的代码块结束 System.out.println("这是second代码块最后不会执行的输出。"); } // second标识的代码块结束 System.out.println("这是first代码块最后会执行的输出。"); } // first标识的代码块结束 } } 程序执行后的结果如下： 这是在执行break second语句之前的输出。 这是first代码块最后会执行的输出。 上面的程序中标识了3个嵌套的代码块，在第三层的third代码块中使用break second;跳出到第二层的second代码块之外。程序执行后，second代码块和third代码块结尾处的输出都因为break语句跳出而没有执行。在编辑器中可以删除if (flag)，不会执行输出的语句会提示Unreachable code。 4.2 continue语句 continue语句适用于任何循环控制结构中，其作用是忽略当前循环体中的剩余代码，让程序立刻跳转，继续执行下一次循环。 在while和do…while循环中，continue会跳转到执行控制循环条件的表达式。 在for循环的传统形式中，continue会跳转到括号中的迭代表达式，然后再执行控制循环条件的表达式。 下面的示例展示了continue语句的用法。 public class ContinueStatement { public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.print(i + " "); if (i % 2 == 0) continue; System.out.println(""); } } } 程序执行后的结果如下： 0 1 2 3 4 5 6 7 8 9 上面的程序使用求模运算符判断每次循环中i值是否为偶数，如果是偶数就继续循环（不执行continue之后输出的换行。 continue语句也可以使用标签名，控制继续执行那个包含它的循环。 下面的示例展示了continue语句指定标识名的用法。 public class ContinueLabelStatement { public static void main(String[] args) { outter: for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { if (j > i) { System.out.println(); continue outter; } System.out.print(j + "×" + i + "=" + (j * i) + " "); } System.out.println(); } } } 程序执行后的结果如下： 1×1=1 1×2=2 2×2=4 1×3=3 2×3=6 3×3=9 1×4=4 2×4=8 3×4=12 4×4=16 1×5=5 2×5=10 3×5=15 4×5=20 5×5=25 1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36 1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49 1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64 1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81 上面的程序和之前for循环的嵌套的示例输出的九九乘法表结果一致。区别主要在于continue语句，终止当前由循环变量j进行计数的内层循环，并继续执行由循环变量i进行计数的外层循环的下一次循环。 4.3 return语句 return语句是Java中最重要的跳转语句，主要有三大关键作用： 终止执行​方法​：立即退出当前方法的执行，忽略剩余代码 ​返回计算​结果​：将计算结果传递给调用者 管理​​执行路径​：改变程序的正常执行流程
一、引言 Java是一种强类型的编程语言，Java中可以使用的数据类型分为基本数据类型（Primitive Types）和引用数据类型（Reference Types）两大类。这两类数据在内存管理、默认值、使用场景等方面存在显著差异。 在Java中，所有变量在使用前必须明确声明类型，声明类型后不可隐式转换变量的类型。这种严格性要求程序员在编写代码时明确数据类型，避免意外错误。Java编译器在编译阶段会进行类型匹配验证，可在代码运行前捕捉类型不匹配问题，显著减少运行时错误。不同基本类型之间的转换需显式强制转换。 Java正是通过编译时检查、显式类型声明和严格转换规则，构建了高可靠性的编程环境。这种设计不仅减少了运行时错误，还提升了代码安全性与可维护性，是Java成为企业级应用首选语言的核心原因之一。 二、基本数据类型 Java中定义了8种基本数据类型：byte、short、int、long、float、double、char、boolean。基本数据类型也被称为简单类型，它们可以分为4种： 整型：包括byte、short、int和long，主要用于表示有符号的整数。 浮点型：包括float和double，主要用于表示带小数位的浮点数。 字符型：char，主要用于表示字符集中的符号，比如字母、数字、标点符号等。 布尔型：boolean，主要用于表示true/false值，常用于逻辑判断。 Java虽然是面向对象的编程语言，但Java的基本数据类型并不是面向对象的，主要因为出于效率原因考虑，因为将基本数据类型设计成对象会降低性能，毕竟对象的结构更加复杂。 Java的基本数据类型具有明确的内存占用空间，也就使其具有明确的范围。这使得Java可以在不同的系统和设备上保持可移植性。比如，不管在哪种操作系统的特定平台上，int类型永远都是使用32个二进制位表示具体的整数。 1.整型（存储整数） 在Java中能够使用的整型类型有4种：byte、short、int和long。它们都能够存储一个有符号的（正号或负号）整数，不同的整型类型在内存中占用不同的二进制位数，使得它们能够表示的整数的范围不同，具体见下表： 类型 位数 取值范围 默认值 典型应用场景 byte 8 -128 ~ 127 0 文件处理、节省内存 short 16 -32768 ~ 32767 0 兼容旧代码、小范围整数 int 32 -2^31 ~ 2^31-1（约±21亿） 0 日常整数运算 long 64 -2^63 ~ 2^63-1 0L 时间戳、大数据量 byte类型是取值范围最小的整型，它使用8位（1字节）表示有符号的整数，只能在-128~127范围内的取整数值。byte特别适合处理来自网络和文件的数据流和其他基本类型不直接兼容的原始二进制数据。 byte类型变量的声明如下所示： byte b;// 将变量b的数据类型声明为byte byte c = 127;// 将变量c的数据类型声明为byte，并同时赋值127 short类型使用16位（2字节）表示有符号的整数，可以在-32768~32767范围内取整数值。相比其他整型类型，short类型的使用率较低。 short类型变量的声明如下所示： short s;// 将变量s的数据类型声明为short short t = 2025;// 将变量t的数据类型声明为short，并同时赋值2025 int类型是最常用的整型类型，它使用32位（4字节）表示有符号的整数，取值范围-2^31~2^31-1（约±21亿），基本可以涵盖日常整数运算的范围。在Java程序中，int类型常用于控制循环次数，或者做数组的索引。另外，由于byte类型和short类型会在表达式计算中自动被提升为int类型，所以当需要使用整型类型时，int类型通常是最佳选项。 int类型变量的声明如下所示： int i;// 将变量i的数据类型声明为int int j = 1745545182;// 将变量i的数据类型声明为int，并同时赋值1745545182 long类型是最大的整型类型，它使用64位（8字节）表示有符号的整数，取值范围-2^63~2^63-1。当需要处理的整数类型过大无法用int类型保存时，就可以使用long类型。long类型的中保存的数值如果超出int类型的范围，就需要在数值末尾增加大写“L”或小写“l”，由于小写“l”在部分字体显示时与数字1过于接近，所以通常建议使用大写“L”作为后缀。 long类型变量的声明如下所示： long l;// 将变量l的数据类型声明为long long m = 1745545182550L;// 将变量m的数据类型声明为long，并同时赋值1745545182550 2.浮点型（存储浮点数） 在Java中能够使用的浮点型类型有2种：float和double。它们用于存储在计算需要小数精度的表达式时使用的浮点数。Java实现了IEEE-754标准机的浮点类型和操作符。这两种不同的浮点型类型在内存中占用不同的二进制位数，使得它们能够表示的浮点数的精度范围不同，具体见下表： 类型 位数 取值范围 默认值 典型应用场景 float 32 1.4E-45~3.4028235E38（6-7位有效数） 0.0F 低精度浮点数运算 double 64 4.9E-324~1.7976931348623157E308（15位有效数） 0.0D 科学计算、默认的浮点型类型 float类型使用32位（4字节）存储单精度浮点数，通常在需要使用小数计算且精度要求不高的场合使用。类似long类型，float类型的数值在保存时需要使用大写“F”或小写“f”作为后缀。 float类型变量的声明如下所示： float f;// 将变量f的数据类型声明为float float g = 3.141593F;// 将变量g的数据类型声明为float，并同时赋值3.141593 注意使用float类型时，由于占用空间较小，使得计算效率较高，但如果操作的数值过大或者过小，就会损失浮点数的精度，导致计算结果偏差过大。 double类型使用64位（8字节）存储双精度浮点数，是Java中使用频率最高的浮点型类型，大部分Java中预定义的方法返回浮点数时，都使用的double类型。虽然double类型相比float类型占用空间更大，如果需要在多次运算中保持浮点数的精度，或者处理非常大的小数时，double类型更具优势。 double类型变量的声明如下所示： double d;// 将变量d的数据类型声明为double double e = 3.14159265358979;// 将变量e的数据类型声明为double，并同时赋值3.14159265358979 3.字符型（存储单个字符） 在Java中使用char类型保存单个字符。Java的设计初衷是允许程序员编写在世界范围内均可运行的程序，而Unicode为了解决传统的字符编码方案的局限，它为全世界每种语言中的每个字符设定了统一并且唯一的二进制编码，以满足跨语言、跨平台进行文本转换、处理的要求，所以Java对Unicode的支持就成为其语言设计的核心特性之一。Java的char类型是​16位无符号整数，取值范围为0~65535，可以直接存储单个字符、ASCII码或Unicode编码。 char类型变量的声明如下所示： char c;// 将变量c的数据类型声明为char char c1 = 'A';// 将变量c1的数据类型声明为char，并同时赋值直接字符'A' char c2 = 65; // 将变量c1的数据类型声明为char，并同时赋值直接字符'A'的ASCII码 char c3 = '\u0041';// 将变量c1的数据类型声明为char，并同时赋值直接字符'A'的Unicode编码 4.布尔型（存储逻辑值） 在Java中使用boolean类型保存逻辑值，它只能保存两个逻辑值true和false其中的一个，所有关系操作符和逻辑操作符都返回boolean类型的值。 boolean类型变量的声明如下所示： boolean b;// 将变量b的数据类型声明为boolean boolean b1 = true;// 将变量b1的数据类型声明为boolean，并同时赋值true boolean b2 = false;// 将变量b2的数据类型声明为boolean，并同时赋值false 5.基本数据类型的字面量 在Java中，​字面量是直接在源代码中表示的固定值，无需通过计算或转换即可使用。字面量主要用于初始化变量、参与表达式运算或定义常量，是程序中数据的基础表示形式。下面依次介绍几种基本数据类型的字面量。 (1).整型字面量 整型是最常用的数据类型，所有整数值都是整型字面量。除了现实世界常用的十进制整数值之外，Java还支持其他进制的整数值作为整型字面量。具体表示方式​如下： 进制 示例 说明 ​十进制​ 10、-5 日常以10位基数描述的整数 ​二进制 0b1010（表示十进制的10） 以0b或0B开头 ​八进制 012（表示十进制的10） 以0开头 ​十六进制 0xA（表示十进制的10） 以0x或0X开头 整型字面量在Java中默认用于创建int类型，当用整型字面量创建其他类型的整型类型时，需要注意： 将整型字面量赋值给byte类型或short类型时，由于这两种整型类型占用的空间比int类型少，如果整型字面量刚好位于byte类型或short类型的取值范围，就可以正常使用不会有问题。 将整型字面量赋值给long类型时，由于long类型占用的空间比int大，如果整型字面量刚好位于int类型的取值范围，可以省略整型字面量末尾的标识（大写“L”或小写“l”），如果整型字面量超出int类型的取值范围，就必须在整型字面量末尾使用标识（大写“L”或小写“l”）明确表示该整型字面量是long类型的。 (2).浮点型字面量 浮点型用于保存具有小数部分的十进制数值，在Java中可以使用标准记数法或者科学记数法表示浮点数。标准记数法是日常生活中最常见的记数法，由整数部分、小数部分和它们之间的小数点组成，如：3.14、0.333、12.27等。科学记数法也称指数记数法，是一种记录或标志数的科学表示法，在数学中，它把一个数表示成a与10的n次幂相乘的形式（1≤|a|<10，a为实数且不为分数形式，n为整数），常用于表示由于太大或太小而不方便用标准记数法表示的数。在Java中，a部分使用标准记数法表示，10的n次幂部分使用大写E（或小写e）后跟上一个十进制整数（表示10的幂），如314159E-5、5.67e11、-6.32E+23等。 浮点型字面量在Java中默认用于创建double类型，当用浮点型字面量创建float类型时，需要在浮点型字面量末尾添加大写F（或小写f）后缀。 虽然也可以在浮点型字面量末尾添加大写D（或小写d）后缀表示使用浮点型字面量创建double类型，但由于浮点型字面量默认就是double类型，所以这样做是多余的。 在Java中还有一种很少用的十六进制浮点型字面量，这种浮点型字面量使用与科学记数法类似的方式表示，不过a部分是一个十六进制数，其后使用大写P（或小写p），再接上一个二进制指数（表示2的幂），如0x12.2P2，0x12.2为十六进制数（转十进制数18.125），P后的2表示2的2次幂（4），所以0x12.2P2代表72.5（18.125乘以4）。 (3).布尔型字面量 布尔型用于保存逻辑值，逻辑值只有true和false两个，所以布尔型字面量只有true和false。在Java中，布尔型字面量只能赋值给声明为布尔型的变量。 在Java中，布尔型不能转换为其他基本数据类型，所以字面量true不能用1代替，字面量false也不能用0代替。 (4).字符型字面量 字符型用于保存单个字符，Java中采用Unicode字符集表示单个字符，所以字符型字面量大部分都可以转换成十六进制的非负整数，并且还可以使用部分操作符进行操作，只要操作结果还是一个十六进制的非负整数即可。在Java中，字符型字面量使用一对单引号包含的字符来表示，如'a'、'汉'、'한'等。对于一些不能直接输入的字符，还可以用转义字符（\）结合一个或多个指定字符组成转义序列进行表示，如'\n'、'\r'、'\t'等。Java还支持使用八进制或十六进制直接表示字符时，对于八进制表示法，可以在转义字符（\）后接三个数字的八进制数，如'\141'表示字符'a'；对于十六进制表示法，可以在转义字符（\）后接u，再接4位的十六进制数，如'\u0061'表示字符'a'，\u2c49'表示字符'汉'，'\ud55c'表示'한'。具体表示方式​如下： 转义序列 说明 描述 \ddd 八进制字符​ 表示一个八进制字符 \uxxxx​ ​十六进制字符​ 表示一个Unicode字符 \n​ 换行符​ 表示新的一行（换行） \r 回车符​ 表示回车（回到一行的开始） \t 制表符​ 表示一个制表符（通常用于对齐文本） \f 换页符​ 表示回车（回到一行的开始） \b 退格符​ 表示退格，将光标移到前一位置 \"​ 双引号​ 表示双引号字符（"）。在字符串中直接使用双引号时，需要用反斜杠进行转义 \' 单引号​ 表示单引号字符（'）。在字符串中直接使用单引号时，需要用反斜杠进行转义 \\ 反斜杠​ 表示反斜杠字符（\）。在字符串中直接使用反斜杠时，需要使用两个反斜杠（\\）来表示一个反斜杠(\) 注意，字符型字面量都使用一对单引号包含的字符来表示，千万不能将单引号写成双引号。在Java中，字符串字面量使用一对双引号包含的若干字符来表示，如："Java"、"编程语言"、"Hello World"等。字符型字面量可以替换字符串字面量中的单个字符，但需要注意的是Java中没有定义徐行的转义字符，所以字符串字面量的开头和结尾必须位于同一行中。另外，不同于其他语言中的字符串通常以字符数组实现，在Java中字符串是引用数据类型。 (5).字面量中的下划线 从Java 7开始，在字面量中引入了下划线（_），主要用于提升代码可读性。 分隔长数字：通过下划线将数字按逻辑分组，例如千分位，使数值更易读。 int decimal = 1_000_000; // 十进制千分位 long creditCard = 1234_5678_9012_3456L; // 十进制千分位 分隔其他进制：按字节或半字节分隔，增强二进制、八进制、十六进制数值的可读性。 int binary = 0b1010_1010; // 二进制 int octal = 0123_4567; // 八进制 int hex = 0xAB_CD_EF; // 十六进制 科学计数法与浮点数：分隔浮点数或科学计数法的整数/小数部分。 double pi = 3.141_592_653_589; // 在小数部分插入下划线 float earthRadius = 6_371.0F; // 在整数部分插入下划线 double avogadro = 6.022_140_857E23; // 科学计数法中插入下划线 在字面量中使用下划线要注意以下使用规则​： ​允许的位置​： 数字之间（整数、浮点数、二进制、十六进制等）。 可连续使用多个下划线（如5______2），但不推。 ​禁止的位置​ 数字开头或结尾（如_1000或1000_）。 小数点前后（如3_.14或3._14）。 进制前缀（如0x、0b）或类型后缀（如 L、F）附近（如 0x_52 或 123_L）。 科学计数法字母E前后之后（如 6.23E1_0）。 三、引用数据类型 在 Java 中，​引用数据类型是面向对象编程的核心，用于表示复杂数据结构。与基本数据类型直接存储数值不同，引用数据类型存储的是对象在堆内存中的地址引用，主要包括类、接口和数组三类。 引用数据类型变量本身存储在栈内存，保存对象的堆内存地址，对象实际数据存储在堆内存，通过引用间接访问。引用数据类型的默认值为null，使用未初始化的引用会抛出空指针异常NullPointerException。 Java中的引用数据类型与基本数据类型的具体区别如下： 特性 基本数据类型 引用数据类型 存储内容 实际数值 对象的内存地址 内存位置 栈内存 变量在栈，对象在堆 默认值 按具体类型区分 null 传递方式 值传递（复制数值） 引用传递（复制地址） 内存占用 固定大小 动态分配（取决于对象复杂度） 操作效率 高 较低（需间接访问堆数据） 垃圾回收 无 自动回收 四、数据类型转换 在Java中，数据类型转换是一个常见的操作，它允许程序员将某种类型的值赋给另一种类型的变量。Java支持两种数据类型类型转换的方式：自动类型转换和强制类型转换。 1.自动数据类型转换 自动数据类型转换也称为隐式转换，当数据类型转换时满足以下条件，那么Java就会将数据类型自动进行转换。 目标类型和源类型是兼容的 目标类型的占用空间大于源类型 当满足上述两个条件时，Java就会自动进行数据类型转换，这种转换被称为扩宽转换（Widening Conversion）。扩宽转换对所有的数值类型（整型和浮点型）都是相互兼容的，那么数值类型的数据是否能够进行扩宽转换，就只需要考虑数据类型的占用空间了。例如，int类型占用32位（4字节），long类型占用64位（8字节），那么将int类型的数据转换为long类型，long类型总是有足够的空间保存int类型的数据，所以Java就会自动进行数据类型转换。 对于基本数据类型，以下是常见的数据类型转换，箭头表示转换的方向： 整数类型：byte → short → int → long → float → double 浮点类型：float → double 字符类型：char → int（转换为Unicode码值） 注意，不存在数值类型到char或boolean类型的自动转换，不存在boolean类型到其他类型的自动转换。 扩宽转换还有一种形式，即混合操作提升，如果操作数中存在占用空间较大的数据类型，结果自动提升为该数据类型。例如，byte、short和char参与算术操作时自动提升为int。 下面的示例展示了Java中的自动数据类型转换。 public class AutoCasting { public static void main(String[] args) { // 1. 整数类型从小到大的自动转换 byte byteVal = 100; short shortVal = byteVal; // byte → short int intVal = shortVal; // short → int long longVal = intVal; // int → long float floatVal = longVal; // long → float（可能损失精度） double doubleVal = floatVal; // float → double System.out.println("byte → short → int → long → float → double:"); System.out.println("byteVal: " + byteVal + " → shortVal: " + shortVal); System.out.println("intVal: " + intVal + " → longVal: " + longVal); System.out.println("floatVal: " + floatVal + " → doubleVal: " + doubleVal); // 2. 字符类型自动转换为整数（Unicode码） char charVal = '汉'; int charToInt = charVal; // char → int（输出27721） System.out.println("\nchar '汉' → int: " + charToInt); // 3. 运算时自动提升到操作数中最大的类型 byte a = 10; short b = 20; int c = 30; long d = 40L; float e = 5.5f; double f = 6.6; // 示例1：byte + short → int int result1 = a + b; System.out.println("\nbyte + short → int: " + result1); // 输出30 // 示例2：int + long → long long result2 = c + d; System.out.println("int + long → long: " + result2); // 输出70 // 示例3：long + float → float float result3 = d + e; System.out.println("long + float → float: " + result3); // 输出45.5 // 示例4：float + double → double double result4 = e + f; System.out.println("float + double → double: " + result4); // 输出12.1 } } 程序执行后的结果如下： byte → short → int → long → float → double: byteVal: 100 → shortVal: 100 intVal: 100 → longVal: 100 floatVal: 100.0 → doubleVal: 100.0 char '汉' → int: 27721 byte + short → int: 30 int + long → long: 70 long + float → float: 45.5 float + double → double: 12.1 2.强制数据类型转换 强制数据类型转换也称为显式转换，因为这种数据类型转换需要使用类型转换操作符明确告诉Java需要进行该数据类型转换。强制数据类型转换的一般形式如下： (target-Data-Type)value 其中，括号中的target-Data-Type指定需要将括号后的value转换成哪种数据类型，value可以是保存了具体数据的变量，也可以是某个具体的值。 在整型类型中，如果需要将内存空间较大的数据类型转换为内容空间较小的数据类型，就需要使用强制数据类型转换，这种转换也被称为缩窄转换（Narrowing Conversion）。例如，int类型占用32位（4字节），long类型占用64位（8字节），那么将long类型的数据转换为int类型，int类型没有足够的空间保存long类型的数据，所以就必须进行缩窄转换。缩窄转换最常见的 后果就是数据溢出，因为原整型数据中的值过大，超出目标整型类型的取值范围，这时原整型数据将按照目标整型类型的取值范围为模减少。 另外，如果将浮点型类型强制转换为整型类型，Java总会对浮点型类型中的浮点数进行截尾，即舍弃浮点数的小数部分。 下面的示例展示了Java中的自动数据类型转换。 public class PrimitiveCasting { public static void main(String[] args) { // 1. 大范围整数转小范围（溢出） long population = 3_000_000_000L; int intPopulation = (int) population; // 结果为-1294967296（溢出） System.out.println("long转int（溢出）: " + intPopulation); // 2. 浮点数转整数（截尾） double price = 99.99; int intPrice = (int) price; // 结果为99 System.out.println("double转int（截尾）: " + intPrice); // 3. 字符与Unicode转换 int unicode = 27721; char restored = (char) unicode; // 结果为'汉' System.out.println("int: " + unicode + ", int转char: " + restored); } } 程序执行后的结果如下： long转int（溢出）: -1294967296 double转int（截尾）: 99 int: 27721, int转char: 汉 五、结语 Java基本数据类型及其转换机制是构建高效、健壮程序的基石。Java的8种基本数据类型（byte、short、int、long、float、double、char、boolean）以简洁的存储结构和明确的取值范围，为数值运算、逻辑判断和字符处理提供了高效支持。自动数据类型转换（如int→long）通过隐式操作提升避免了精度丢失，而强制数据类型转换（如double→int）则需谨慎处理截尾和溢出风险。从char的Unicode支持到long的大整数处理，Java的数据类型设计始终兼顾国际化与工程实践需求。而类型转换的严格规则（如boolean不可与其他类型互转）则体现了语言对逻辑严谨性的追求。理解基本数据类型及其转换机制，不仅是避免NullPointerException或精度丢失等常见问题的关键，更是优化程序性能、提升代码可维护性的核心技能。
一、引言 操作符在编程中的核心地位 操作符是程序中对数据进行计算、判断和转换的核心工具。操作符定义数据被处理方式，将原始输入的数据（操作数）转化为有意义的中间结果，为程序逻辑提供计算能力。在Java编程中，操作符是实现数据运算和赋值的关键。对于初学者来说，理解和掌握各种操作符的用法和特性是非常重要的。 Java操作符的设计哲学与特性 Java操作符的设计哲学以简约性、强类型安全和封装性为核心，通过严格规范操作符行为与优先级，确保代码的可预测性和可维护性。理解Java操作符的特性，有助于开发者规避常见陷阱（如整数除法截断、别名问题），并充分利用位运算等特性优化性能关键代码。 二、Java操作符分类与详解 Java中的操作符，按照操作数被处理的方式，可以分为算术操作符、赋值操作符、位操作符、关系操作符、逻辑操作符以及三元操作符。不管哪种操作符，都可以通过一个或多个操作数来生成新的值，有的操作符还可以修改其操作数的值。另外，几乎所有的操作符（除开=、==、!=）都只能操作基本数据类型的操作数。 1.算术操作符 Java的算数操作符与其他大部分编程语言相同，包括加法（+）、减法（-）、乘法（*）、除法（/）、求模（%）和自增（++）、自减（--），具体见下表： 操作符 名称 示例 功能描述 + 加法操作符 a + b 加法 - 计算操作符两侧的值的和 - 减法操作符 a - b 减法 - 计算操作符左侧的值减去右侧的值的差 * 乘法操作符 a * b 乘法 - 计算操作符两侧的值的积 / 除法操作符 a / b 除法 - 计算操作符左侧的值除以右侧的值的商 % 求模操作符 a % b 求模 - 计算操作符左侧的值除以右侧的值的余数 ++ 自增操作符 a++或++a 操作数的值增加1 -- 自减操作符 a--或--a 操作数的值增减1 下面的示例展示了在四则运算中，部分算数操作符（+、-、*、/、%）的用法。 import java.util.Random; public class MathOps { public static void main(String[] args) { // 创建Random对象，为后续四则运算生成随机数: Random rand = new Random(27); int i, a, b; // 生成随机整数a和b并进行四则运算: a = rand.nextInt(100) + 1; System.out.println("a : " + a); b = rand.nextInt(100) + 1; System.out.println("b : " + b); i = a + b; System.out.println("a + b : " + i); i = a - b; System.out.println("a - b : " + i); i = a / b; System.out.println("a / b : " + i); i = a * b; System.out.println("a * b : " + i); i = a % b; System.out.println("a % b : " + i); a %= b; System.out.println("a %= b : " + a); // 生成随机浮点数s和n并进行四则运算: double u, s, n; s = rand.nextDouble(); System.out.println("s : " + s); n = rand.nextDouble(); System.out.println("n : " + n); u = s + n; System.out.println("s + n : " + u); u = s - n; System.out.println("s - n : " + u); u = s * n; System.out.println("s * n : " + u); u = s / n; System.out.println("s / n : " + u); s %= n; System.out.println("s % n : " + s); } } 程序执行后的结果如下： a : 31 b : 53 a + b : 84 a - b : -22 a / b : 0 a * b : 1643 a % b : 31 a %= b : 31 s : 0.7146368532850442 n : 0.5047406370101457 s + n : 1.21937749029519 s - n : 0.20989621627489852 s * n : 0.36070626055801924 s / n : 1.4158496480850609 s % n : 0.20989621627489852 上面的程序首先创建了一个Random对象，后续进行算术运算的整数和浮点数都通过调用Random对象的nextInt()方法和nextDouble()方法生成。但执行多次程序会发现每次生成的随机数都一样（所以运算结果也一样）。这是因为在创建Random对象，传递了随机种子（示例中是整数27），相同的随机种子会保证按照相同的序列生成随机数。如果去除创建Random对象的随机种子，那么Java会使用操作系统的当前时间作为随机种子。另外，Random对象除了nextInt()方法和nextDouble()方法之外，还有用于生成其他基本数据类型（除了char类型）的方法，方法名为next单词在前，首字母大写的基本数据类型名称在后。 一元加(+)和一元减（-）与二元加减法使用了相同的符号，Java会根据+、-操作符前后的操作数自行判断运算方式。一元加（+）会将比int类型小的数据类型（byte或者short）的操作数转换为int类型，一元减（-）会反转数据的正负值。 下面的示例展示了自增（++）、自减（--）运算符的用法： public class AutoInc { public static void main(String[] args) { int i = 1; System.out.println("i: " + i); System.out.println("++i: " + ++i); // 前缀式自增 System.out.println("i++: " + i++); // 后缀式自增 System.out.println("i: " + i); System.out.println("--i: " + --i); // 前缀式自减 System.out.println("i--: " + i--); // 后缀式递减 System.out.println("i: " + i); } } 程序执行后的结果如下： i: 1 ++i: 2 i++: 2 i: 3 --i: 2 i--: 2 i: 1 自增（++）、自减（--）操作符都有两种使用方式，一种是放在操作数之前，称为前缀式自增或前缀式自减，如++i或--i，另一种是放在操作数之后，称为后缀式自增或后缀式自减，如i++或i--。 自增（++）、自减（--）操作符对操作数的影响无论前缀式还是后缀式，都一样。假如i是一个int类型的数据，i++和++i，对i值的影响都等价于i = i + 1，同理，i--和--i，对i值的影响都等价于i = i - 1。 那么自增（++）、自减（--）运算符作为前缀和后缀到底有什么区别呢？其实它们的区别主要在于整个表达式的值。前缀式自增或前缀式自减会先对操作数进行自增或自减操作，然后将操作后的结果作为整个表达式的值，后缀式自增或后缀式自减会先把操作数作为整个表达式的值，然后对操作数进行自增或自减的操作。这样，就可以上面程序的运行结果了。int类型的变量i初始保存整数1，经过两次自增（++i和i++）之后值变为3，再经过两次自减（--i和i--）之后值变为1。两次自增的操作返回表达式++i和i++的值虽然相同，但其中i值是不同的；同理，两次自减操作返回表达式--i和i--虽然相同，但其中的i值也是不同的。 2.赋值操作符 赋值操作符（=）用于进行赋值操作。赋值操作符（=）的操作过程就是，取=右边的数据（通常简称右值），将其保存到=左边的变量（通常简称左值）。需要注意的是，在赋值操作过程中，右值可以是其他变量、常量或者是表达式，但左值只能是命名的变量。比如，int i = 0;表示把整数0保存到整型变量i中，10 = a这种写法就是错误的。 赋值操作符（=）操作的右值是基本数据类型时比较好理解，因为基本数据类型的数据是直接保存在左值的变量中，例如int a = 0; int b = a;中b = a就是直接将a中保存的0保存到b中。后续操作a或者b都不影响另一个变量中的数据，大多数情况下这也是我们希望的。 但赋值操作符（=）操作的右值如果是引用数据类型，就需要注意了，因为引用数据类型的数据保存在左值中的是对该对象的引用。例如Object a = new Object();Object b = a;中b = a实际上是将a指向的对象的引用，复制了一个相同的引用到b。后续操作a或者b都会影响另一个变量的数据，因为它们实际上都指向同一个对象。下面这个示例将演示这种情况： class Dog { int age; } public class Assignment { public static void main(String[] args) { Dog dog001 = new Dog(); dog001.age = 3; Dog dog002 = new Dog(); dog002.age = 5; System.out.println("1:dog001.age=" + dog001.age + ",dog002.age=" + dog002.age); dog001 = dog002; System.out.println("2:dog001.age=" + dog001.age + ",dog002.age=" + dog002.age); dog001.age = 7; System.out.println("3:dog001.age=" + dog001.age + ",dog002.age=" + dog002.age); } } 程序执行后的结果如下： 1:dog001.age=3,dog002.age=5 2:dog001.age=5,dog002.age=5 3:dog001.age=7,dog002.age=7 Dog类只有一个成员变量age保存整型数据。它的两个实例dog001和dog002都在main()方法中实例化出来，每个实例的age属性都保存了一个不同的整型数据。然后dog001 = dog002;将dog002赋值给了dog001，接着修改了dog001的age。由于赋值操作符在dog001 = dog002;中操作的是引用数据类型，它实际上让dog001和dog002指向了一个相同的Dog实例，即原来的dog002，而原来的dog001实例因为不再被引用而被垃圾回收机制清理掉。这种情况在编程中通常成为“别名”，也是Java操作对象的一种基本方式。如果想避免对象出现别名，可以将dog001 = dog002;替换为dog001.age = dog002.age;，这样就可以保持dog001和dog002两个实例独立，避免别名将dog001和dog002指向同一个Dog的实例。 注意上面避免别名的方式中，直接操作了对象的属性，这种操作方式不符合Java面向对象的封装特性：隐藏对象的属性和实现细节，仅对外提供公有的访问方式。 赋值操作符（=）还可以和其他部分操作符合并使用，这种合并使用的操作符被称为复合赋值操作符。它将算术操作、位操作与赋值操作结合为单一操作符。 算术复合赋值操作符 通用形式为“算数操作符=”，具体见下表： 操作符 名称 示例 说明 += 加法赋值 a += 5 等价于 a = a + 5 -= 减法赋值 a -= 3 等价于 a = a - 3 *= 乘法赋值 a *= 2 等价于 a = a * 2 /= 除法赋值 a /= 4 等价于 a = a / 4 %= 取模赋值 a %= 5 等价于 a = a % 5 位运算复合赋值操作符 通用形式为“算数操作符=”，具体见下表： 操作符 名称 示例 说明 &= 按位与赋值 a += 5 等价于 a = a + 5 |= 按位或赋值 a |= b 等价于 a = a | b ^= 按位异或赋值 a ^= b 等价于 a = a ^ b <<= 左移赋值 a <<= 2 等价于 a = a << 2 >>= 带符号右移赋值 a >>= 1 等价于 a = a >> 1 >>>= 无符号右移赋值 a >>>= 1 等价于 a = a >>> 1 Java的复合赋值操作符通过简化表达式和隐式优化，成为提升代码效率的核心工具，涵盖算术操作和位操作，支持自动类型转换，但需警惕数据溢出和优先级陷阱。 另外+和+=还有一种特殊的用法，它们可以对Java中的字符串String对象进行连接操作。但当字符串String对象与其他数据类型进行连接时，一定要注意数据类型转换，下面这个示例将演示这种情况： public class StringOperation { public static void main(String[] args) { int a = 0, b = 1, c = 2; String s = "a, b, c "; System.out.println(s + a + b + c); // 变量a, b, c转换为字符串 System.out.println(a + " " + s); // 变量a转换为字符串 s += "(summed) = "; // 字符串s连接新的字符串 System.out.println(s + (a + b + c));// 变量a, b, c中的整型求和 System.out.println("" + a);// 变量a转换为字符串，Integer.toString()的简化版 } } 程序执行后的结果如下： a, b, c 012 0 a, b, c a, b, c (summed) = 3 0 注意第一个输出内容中的012，实际上是s和a，b，c中的0，1，2被转化的字符串进行了连接操作。第二个输出内容中的0也是a中的数值被转换为字符串后与s进行了连接操作。因此使用字符串连接操作符时，其他数据类型是否转换为字符串并不取决于先后顺序。第三个输出内容通过()改变了a + b + c的优先级，所以优先进行了求和才被转换为字符串。第四个输出内容使用了"" + a的形式进行输出，实际上这是一种针对基本数据类型进行转换的方式，它等效于调用a的基本数据类型对应的包装类的toString()方法。 3.位操作符 前面在符合赋值操作符中见到的位操作符主要用于操作二进制数据。位操作符源自C语言，由于C语言面向底层的特性，它经常需要直接操作底层硬件寄存器中的二进制位。Java也用位操作符实现了C语言中类似的底层操作。通过对整数类型（byte、short、int、long）的二进制位进行运算，可实现高性能计算、内存优化等场景需求。 Java支持的位操作符按照对二进制位的操作方式主要分为按位操作和移位操作，具体见下表： 操作符 名称 分类 示例 功能描述 & 按位与 按位 a & b 两个对应位均为1时结果为1，否则为0 | 按位或 按位 a | b 任意一个对应位为1时结果为1 ^ 按位异或 按位 a ^ b 对应位不同时结果为1，相同时为0 ~ 按位非 按位 ~a 每一位取反（0变1，1变0） << 左移 移位 a << n 所有位左移n位，低位补0，相当于乘以2ⁿ >> 带符号右移 移位 a >> n 所有位右移n位，高位补符号位（正数补0，负数补1），相当于除以2ⁿ并向下取整 >>> 无符号右移 移位 a >>> n 所有位右移n位，高位补0 上表中只有按位非操作符（~）是一元操作符，它只对其右侧的操作数进行按位取反操作，所以它不能跟赋值操作符结合成复合赋值操作符使用。其他的位操作符都是二元操作符，可以跟赋值操作符结合成复合赋值操作符。按位与（&）、按位或（|）使用了和逻辑与（&&）、逻辑或（||）相同的字符，使用时一定要注意区分。 字符类型（char）由于可以转换成整型，也可以使用位操作符操作。 布尔类型（boolean）在被位操作符操作时，也需要注意，只能对布尔值进行按位与（&）、按位或（|）和按位异或（^）操作，不能对其进行按位非（~）操作，需要对布尔值取反只能使用逻辑非（!）。另外，对布尔值进行按位与（&）、按位或（|）操作时，效果和逻辑与（&&）、逻辑或（||）相同，但按位与（&）、按位或（|）不会“短路”。 下面这个示例将演示所有位操作符的操作情况： import java.util.Random; public class BitOperation { public static void main(String[] args) { Random rand = new Random(27); printBinary("-1", -1); printBinary("+1", +1); int maxInt = 2147483647; printBinary("maxInt", maxInt); int minInt = -2147483648; printBinary("minInt", minInt); int i = rand.nextInt(); printBinary("i", i); printBinary("~i", ~i); printBinary("-i", -i); printBinary("i << 5", i << 5); printBinary("i >> 5", i >> 5); printBinary("(~i) >> 5", (~i) >> 5); printBinary("i >>> 5", i >>> 5); printBinary("(~i) >>> 5", (~i) >>> 5); int j = rand.nextInt(); printBinary("j", j); printBinary("i & j", i & j); printBinary("i | j", i | j); printBinary("i ^ j", i ^ j); printBinary("-1L", -1L); printBinary("+1L", +1L); long maxLong = 9223372036854775807L; printBinary("maxLong", maxLong); long minLong = -9223372036854775808L; printBinary("minLong", minLong); long l = rand.nextLong(); printBinary("l", l); printBinary("~l", ~l); printBinary("-l", -l); printBinary("l << 5", l << 5); printBinary("l >> 5", l >> 5); printBinary("(~l) >> 5", (~l) >> 5); printBinary("l >>> 5", l >>> 5); printBinary("(~l) >>> 5", (~l) >>> 5); long m = rand.nextLong(); printBinary("m", m); printBinary("l & m", l & m); printBinary("l | m", l | m); printBinary("l ^ m", l ^ m); } static void printBinary(String s, int i) { String BinaryStr = Integer.toBinaryString(i); while(BinaryStr.length() < 32){ BinaryStr = "0"+BinaryStr; } System.out.println(s + ", int: " + i + ", binary:\n " + BinaryStr); } static void printBinary(String s, long l) { String BinaryStr = Long.toBinaryString(l); while(BinaryStr.length() < 64){ BinaryStr = "0"+BinaryStr; } System.out.println(s + ", long: " + l + ", binary:\n " + BinaryStr); } } 程序执行后的结果如下： -1, int: -1, binary: 11111111111111111111111111111111 +1, int: 1, binary: 00000000000000000000000000000001 maxInt, int: 2147483647, binary: 01111111111111111111111111111111 minInt, int: -2147483648, binary: 10000000000000000000000000000000 i, int: -1152021836, binary: 10111011010101011000101010110100 ~i, int: 1152021835, binary: 01000100101010100111010101001011 -i, int: 1152021836, binary: 01000100101010100111010101001100 i << 5, int: 1790006912, binary: 01101010101100010101011010000000 i >> 5, int: -36000683, binary: 11111101110110101010110001010101 (~i) >> 5, int: 36000682, binary: 00000010001001010101001110101010 i >>> 5, int: 98217045, binary: 00000101110110101010110001010101 (~i) >>> 5, int: 36000682, binary: 00000010001001010101001110101010 j, int: 1761585305, binary: 01101000111111111010100010011001 i & j, int: 676694160, binary: 00101000010101011000100010010000 i | j, int: -67130691, binary: 11111011111111111010101010111101 i ^ j, int: -743824851, binary: 11010011101010100010001000101101 ……此处省略长整型（long）数据的操作结果 在上面的示例代码中，BitOperation类中，main()方法之外定义了两个无返回值的静态方法printBinary()，它们的方法名相同，但通过第二个参数的数据类型（int或者long）进行方法重载，将传入的int类型参数或者long类型参数通过其包装类的toBinaryString()方法转换成字符串，由于toBinaryString()方法在转换字符串时会忽略数值的二进制位高位中的前导0，所以方法中通过一个while循环对转换之后字符串补充高位0。这个示例对int和long类型的数值使用所有的位操作符进行了操作并展示了结果操作结果，此外，还展示了int和long的最大值、最小值、正1值和负1值的二进制形式。 注意所有整型数据都用二进制形式的最高位表示符号：0表示正数，1表示负数。 4.关系与逻辑操作符 关系与逻辑操作符返回的操作结果都是布尔值。 关系操作符会根据操作符左右两侧的操作数的值之间的关系（大小或相等），返回布尔值（true/false）。如果关系为真，关系表达式会返回true；如果关系不为真，关系表达式会返回false。关系操作符是控制程序逻辑的核心工具，常用于条件判断和循环控制。 关系操作符包括小于（<）、大于（>）、小于等于（<=）、大于等于（>=）、相等于（==）和不相等于（!=），具体见下表： 操作符 名称 示例 功能描述 < 小于操作符 a < b 检查左侧操作数的值是否小于右侧操作数的值 > 大于操作符 a > b 检查左侧操作数的值是否大于右侧操作数的值 <= 小于等于操作符 a <= b 检查左侧操作数的值是否小于等于右侧操作数的值 >= 大于等于操作符 a >= b 检查左侧操作数的值是否大于等于右侧操作数的值 == 相等于操作符 a == b 检查左侧操作数的值是否相等于右侧操作数的值 != 不相等于操作符 a != b 检查左侧操作数的值是否不相等于右侧操作数的值 下面这个示例将演示所有关系操作符的操作情况： import java.util.Random; public class RelationalOperation { public static void main(String[] args) { Random rand = new Random(27); int a = rand.nextInt(); int b = rand.nextInt(); System.out.println("a: " + a); System.out.println("b: " + b); System.out.println("a < b : " + (a < b)); System.out.println("a > b : " + (a > b)); System.out.println("b <= a : " + (b <= a)); System.out.println("b >= a : " + (b >= a)); System.out.println("a == b : " + (a == b)); System.out.println("a != b : " + (a != b)); } } 程序执行后的结果如下： a: -1152021836 b: 1761585305 a < b : true a > b : false b <= a : false b >= a : true a == b : false a != b : true 小于（<）、大于（>）、小于等于（<=）和大于等于（>=）适用于除布尔类型（boolean）之外的基本数据类型，因为布尔类型只有true和false两个值，对这两个值比较大小没有意义；相等于（==）和不相等于（!=）适用于所有基本数据类型。 相等于（==）和不相等于（!=）还适用于所有引用数据类型，但在操作引用数据类型时，它们的返回结果可能会引起困惑。 下面这个示例将演示相等于（==）操作符对引用数据类型的操作情况： public class EqualsOperation { static void show(String desc, Integer n1, Integer n2) { System.out.println(desc + ":"); System.out.printf("%d==%d的返回值：%b，换用equals()时的返回值：%b%n", n1, n2, n1 == n2, n1.equals(n2)); } public static void testEquals(int value) { // [1] 自动转换为Integer对象 Integer i1 = value; Integer i2 = value; show("[1]自动转换", i1, i2); // [2] 使用Integer的构造方法实例化Integer对象 Integer r1 = new Integer(value); Integer r2 = new Integer(value); show("[2]构造方法", r1, r2); // [3] 使用Integer.valueOf()方法创建Integer对象 Integer v1 = Integer.valueOf(value); Integer v2 = Integer.valueOf(value); show("[3]Integer.valueOf()", v1, v2); // [4] 基本数据类型int int x = value; int y = value; // x.equals(y); // 基本数据类型不能使用equals()比较 show("[4]基本数据类型int", x, y); } public static void main(String[] args) { testEquals(127); testEquals(128); } } 程序执行后的结果如下： [1]自动转换: 127==127的返回值：true，换用equals()时的返回值：true [2]构造方法: 127==127的返回值：false，换用equals()时的返回值：true [3]Integer.valueOf(): 127==127的返回值：true，换用equals()时的返回值：true [4]基本数据类型int: 127==127的返回值：true，换用equals()时的返回值：true [1]自动转换: 128==128的返回值：false，换用equals()时的返回值：true [2]构造方法: 128==128的返回值：false，换用equals()时的返回值：true [3]Integer.valueOf(): 128==128的返回值：false，换用equals()时的返回值：true [4]基本数据类型int: 128==128的返回值：false，换用equals()时的返回值：true 在上面的示例代码中，EqualsOperation类中的main()方法调用testEquals()方法，testEquals()方法将传入的整型数据按照不同的方式转换为Integer对象，再将转换后的两个Integer对象通过参数传递给show()方法，在show()方法中分别使用相等于（==）操作符和equals()方法对两个Integer对象进行比较并输出结果。 如果要查看不相等于（!=）操作符，可以将show()方法中的相等于（==）操作符替换为不相等于（!=）操作符，并将n1.equals(n2)修改为!n1.equals(n2)。 需要注意testEquals()方法中将接受的整型参数转换为Integer对象的几种不同方式： [1] 自动转换为Integer对象，本质上是通过调用Integer.valueOf()方法创建Integer对象。 [2] 使用Integer的构造方法实例化Integer对象，这种方式在Java 9之后的版本中已经不推荐使用。它是以前使用包装类进行装箱操作的主要方式。 [3] 使用Integer.valueOf()方法创建Integer对象，从Java 9开始推荐的使用包装类进行装箱操作的方式，它的效率优于方式[2]，但代码较[1]复杂。 [4] 基本数据类型int也可以直接当做参数传递给show()，虽然show()方法接受的参数类型是Integer。 在程序执行后的结果中，127的比较结果相对容易理解，传递给show()方法的4种参数都产生了预期的结果。方式[2]中的相等于（==）操作符返回了false是因为虽然两个Integer对象保存的数据都是127，但两个Integer对象是各自通过构造方法创建的，所以它们通过相等于（==）操作符比较的引用地址是不同的。但128的比较结果就容易让人疑惑，转换为Integer对象之后，所有保存了128的对象通过相等于（==）操作符返回的结果都是false，所有通过equals()方法比较返回的结果都是true。这是因为在使用Integer对象时，Java会通过享元模式来缓存数值范围在-128到+127内的对象，因此在这个范围内无论使用多少次方式[1]和方式[3]来创建Integer对象，只要数值相同，它们引用的都是同一个Integer对象。而超出这个数值范围，即使数值相同，Java也会实例化新的Integer对象。所以，通常都会建议：使用操作符操作基本数据类型，使用equals()方法操作引用数据类型。 另外使用关系操作符操作浮点数时，也会遇到一些返回结果超出预期的情况，下面这个示例将演示相等于（==）操作符对浮点数的操作情况： public class EqualsDouble { static void show(String desc, Double n1, Double n2) { System.out.println(desc + ":"); System.out.printf("%e==%e的返回值：%b，换用equals()时的返回值：%b%n", n1, n2, n1 == n2, n1.equals(n2)); } public static void testEquals(double value1, double value2) { // [1] 自动转换为Double对象 Double d1 = value1; Double d2 = value2; show("[1]自动转换", d1, d2); // [2] 使用Double的构造方法实例化Double对象 Double r1 = new Double(value1); Double r2 = new Double(value2); show("[2]构造方法", r1, r2); // [3] 使用Double.valueOf()方法创建Integer对象 Double v1 = Double.valueOf(value1); Double v2 = Double.valueOf(value2); show("[3]Double.valueOf()", v1, v2); // [4] 基本数据类型double double x = value1; double y = value2; // x.equals(y); // 基本数据类型不能使用equals()比较 show("[4]基本数据类型int", x, y); } public static void main(String[] args) { testEquals(0, Double.MIN_VALUE); testEquals(Double.MAX_VALUE, Double.MAX_VALUE - Double.MIN_VALUE * 1_000_000); } } 程序执行后的结果如下： [1]自动转换: 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [2]构造方法: 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [3]Integer.valueOf(): 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [4]基本数据类型int: 0.000000e+00==4.900000e-324的返回值：false，换用equals()时的返回值：false [1]自动转换: 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true [2]构造方法: 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true [3]Integer.valueOf(): 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true [4]基本数据类型int: 1.797693e+308==1.797693e+308的返回值：false，换用equals()时的返回值：true 在上面的示例代码中，沿用EqualsOperation类的代码，只是将需要比较的数据类型换成了Double对象。代码中的Double.MIN_VALUE和Double.MAX_VALUE分别表示Double对象可以表示的最小浮点数和最大浮点数，为了方便查看输出结果，在格式化输出内容时使用了e%指定数值使用科学计数法方式输出。第一次比较0和最小浮点数，两个数值明显不同，所以返回结果false还在预想之中，但第二次比较最大浮点数，和最大浮点数减去一百万倍的最小浮点数，从数学角度看，这两个数值明显不同，但equals()方法都返回了true值。这是由浮点数在计算机中的特性决定的，当一个非常大的数值减去一个相对较小的数值时，这个非常大的数值不会发生明显变化，这就是编程语言中的“舍入误差”，这种误差产生的原因主要是因为计算机不能用足够的二进制位数精确的表示一个非常大的数值的微小变化。另外，相等于（==）操作符返回的结果都是false，是因为相等于（==）操作符比较的是Double对象的引用地址。 基于上面的两个示例，在进行关系操作时，通常都会建议：使用操作符操作基本数据类型，使用equals()方法操作引用数据类型。但是需要注意的是，并不是每个引用数据类型都重写了equals()方法。大部分标准库会重写equals()方法来比较对象的内容而非对象的引用。 逻辑操作符用于根据操作数的逻辑关系，进行组合或修改，返回新的布尔值。逻辑操作符是构建复杂条件判断的基础。 逻辑操作符包括逻辑与（&&）、逻辑或（||）和逻辑非（!），具体见下表： 操作符 名称 示例 功能描述 && 逻辑与操作符 a && b 当且仅当左右两个操作数都为true，才返回true || 逻辑或于操作符 a || b 左右两个操作数任一为true，就返回true ! 逻辑非操作符 !a 用来反转操作数的逻辑状态，将true反转为false，将false反转为true 下面这个示例将演示关系操作符和逻辑操作符的操作情况： import java.util.Random; public class LogicOperation { public static void main(String[] args) { Random rand = new Random(27); int a = rand.nextInt(); int b = rand.nextInt(); System.out.println("a: " + a); System.out.println("b: " + b); System.out.println("a < b : " + (a < b)); System.out.println("a > b : " + (a > b)); System.out.println("b <= a : " + (b <= a)); System.out.println("b >= a : " + (b >= a)); System.out.println("a == b : " + (a == b)); System.out.println("a != b : " + (a != b)); // Java中不允许将int类型当作boolean类型使用，以下注释的代码是错误的: // System.out.println("a && b : " + (a && b)); // System.out.println("a || b : " + (a || b)); // System.out.println("!a : " + !a); System.out.println("(a < 10) && (b < 10) : " + ((a < 10) && (b < 10))); System.out.println("(a < 10) || (b < 10) : " + ((a < 10) || (b < 10))); } } 程序执行后的结果如下： a: -1152021836 b: 1761585305 a < b : true a > b : false b <= a : false b >= a : true a == b : false a != b : true (a < 10) && (b < 10) : false (a < 10) || (b < 10) : true 在上面的示例代码中，注释的代码内容是错误的用法，因为在Java中，逻辑与（&&）、逻辑或（||）和逻辑非（!）只能用于操作boolean类型的数据，所以即使将示例代码中的int类型替换为其他除boolean之外的任何基本数据类型，代码都可以正常运行。示例代码的最后两个输出内容，先由关系操作符比较变量a、b和10的大小关系，再由逻辑操作符根据返回的布尔值结果进行逻辑操作，最终返回逻辑操作的布尔值结果。 逻辑与（&&）和逻辑或（||）在进行逻辑操作时，还具有“短路”的特性，这种特性导致它们在进行逻辑操作是，一旦表达式的部分操作结果能够确定整个表达式的返回值，那么表达式剩余的部分就不会继续操作。比如，condition1 && condition2，如果condition1返回了false，无论condition2返回的布尔值是什么，整个表达式的值都已经由逻辑与（&&）左侧的false确定了整个表达式的返回值是false，那么condition2就不会被执行。同样，condition1 || condition2，如果condition1返回了true，无论condition2返回的布尔值是什么，整个表达式的值都已经由逻辑或（||）左侧的true确定了整个表达式的返回值是true，那么condition2就不会被执行。 下面这个示例将演示逻辑与（&&）和逻辑或（||）的短路操作情况： public class LogicShortCircuit { static boolean condition1(int val) { System.out.println("condition1(" + val + ")"); System.out.println("返回的结果是: " + (val < 1)); return val < 1; } static boolean condition2(int val) { System.out.println("condition2(" + val + ")"); System.out.println("返回的结果是: " + (val < 2)); return val < 2; } static boolean condition3(int val) { System.out.println("condition3(" + val + ")"); System.out.println("返回的结果是: " + (val < 3)); return val < 3; } public static void main(String[] args) { boolean b1 = condition1(0) && condition2(2) && condition3(2); System.out.println("表达式condition1(0) && condition2(2) && condition3(2)"); System.out.println("返回的结果是: " + b1); boolean b2 = condition1(0) || condition2(2) || condition3(2); System.out.println("表达式condition1(0) || condition2(2) || condition3(2)"); System.out.println("返回的结果是: " + b2); } } 程序执行后的结果如下： condition1(0) 返回的结果是: true condition2(2) 返回的结果是: false 表达式condition1(0) && condition2(2) && condition3(2) 返回的结果是: false condition1(0) 返回的结果是: true 表达式condition1(0) || condition2(2) || condition3(2) 返回的结果是: true 在上面的示例代码中，condition1()、condition2()和condition3()分别将接受到的整型参数同0，1和2进行小于比较，并返回结果。表达式condition1(0) && condition2(2) && condition3(2)在计算到condition2(2)时得到了false，就将false作为整个表达式的返回值并结束操作，所以condition3(2)被短路未执行；同理，表达式condition1(0) || condition2(2) || condition3(2)在计算到condition1(0)时得到了true，就将true作为整个表达式的返回值并结束操作，所以condition2(2)和condition3(2)被短路未执行。Java的逻辑短路特性可以快速返回逻辑操作结果，从某种程度上看，还可以减少系统资源开销。但是需要注意避免在可能逻辑短路的表达式中对数据进行操作。 5.三元操作符 三元操作符比较特殊，它有三个操作数，不过它确实是一个操作符，因为它会根据它的三个操作数返回一个操作结果。三元表达式的形式如下： expression ? value0 : value1 如果expression的操作结果是布尔值true，value0就会返回操作结果，这个操作结果会作为整个三元表达式的操作结果；反之，如果expression的操作结果是布尔值false，value1就会返回操作结果，这个操作结果会作为整个三元表达式的操作结果。所以，三元操作符也常被称为条件操作符。但三元操作符与程序流程控制中的if…else…语句仍要进行区分，因为三元操作符会返回操作结果。 下面这个示例将演示三元操作符的操作情况： public class TernaryOperation { static int ternary(int i) { return (i < 10) ? i * 10 : i / 2; } static int standardIfElse(int i) { if (i < 10) return i * 10; else return i / 2; } public static void main(String[] args) { System.out.println(ternary(9)); System.out.println(ternary(10)); System.out.println(standardIfElse(9)); System.out.println(standardIfElse(10)); } } 程序执行后的结果如下： 90 5 90 5 在上面的示例代码中，ternary()方法接受int类型参数，将接收的参数值通过三元操作符处理并返回三元表达式的返回值，standardIfElse()方法接受int类型参数，通过if…else…语句判断接收的参数值是否小于10，再决定返回哪一个计算结果。这两个方法相比，三元操作符使得代码更加简洁，if…else…语句更容易理解，各有优势，但三元操作符更适合需要从两个值选择一个赋值给变量的操作场景。 6.操作符的优先级和结合性 当一个表达式中出现了多个操作符时，操作符的优先级会决定表达式的操作顺序。操作符的结合性则决定了操作数的操作方向。 优先级 操作符名称 操作符说明 结合性 1 () 分隔符 从左向右 2 ! +（一元正） -（一元负） ~ ++ -- 一元操作符 从右向左 3 * / % 算术操作符 从左向右 4 +（加法） -（减法） 算术操作符 从左向右 5 << >> >>> 移位操作符 从左向右 6 < <= >= > instanceof 关系操作符 从左向右 7 == != 关系操作符 从左向右 8 & 按位操作符 从左向右 9 ^ 按位操作符 从左向右 10 | 按位操作符 从左向右 11 && 逻辑操作符 从左向右 12 || 逻辑操作符 从左向右 13 ?: 条件操作符 从右向左 14 = += *= /= %= &= ^= <<= >>= >>>= 赋值操作符 从右向左 在编写复杂的表达式时，如果不确定操作符的优先级，可以使用括号来明确指定操作的顺序。例如，表达式 a + b * c 中，乘法操作 b * c 会先于加法操作执行，因为乘法操作符的优先级高于加法操作符。如果想要先执行加法操作，则应该写成 (a + b) * c。在实际编程中，合理利用括号来避免优先级引起的错误是一个好习惯。 在使用按位操作符和移位操作符时，需要特别注意它们的优先级，因为它们可能会影响到数值的正负和大小。此外，逻辑操作符 （&&）和（||）具有短路特性，即在确定整个逻辑表达式的结果后，可能不会操作所有的操作数。
一、安装Java开发工具包（JDK） 1.Java与JDK的关系 要学习Java编程语言，需要在操作系统中安装JDK。可以降Java语言和JDK之间的关系理解为： Java语言定义了语法规则和功能特性，而JDK提供将这些规则转化为实际程序的工具集合。 JDK中主要包含： 编译器（javac）：将Java源代码（.java文件）编译为字节码（.class文件），这是Java跨平台的基础。 运行时环境（JRE）：包含JVM（Java虚拟机）和核心类库，负责执行编译后的字节码。 调试工具（jdb） 和文档生成器（javadoc） 等：支持开发全流程。 所以没有JDK，Java语言编写代码就无法编译和执行；没有Java语言的规范，JDK就失去存在意义。 以下是Java历代主要版本与JDK版本的对应关系： Java版本 发布时间 对应JDK版本 备注 Java1.0 1995.05.23 JDK1.0 首个正式版，提出“一次编写，到处运行”理念 Java1.1 1997.02.18 JDK1.1 引入内部类、JDBC等特性，JavaOne会议规模创纪录 Java1.2 1998.12.04 JDK1.2 划分为J2SE、J2EE、J2ME三大平台，奠定企业级应用基础(2005年6月正式取消，统一更名为Java SE、Java EE、Java ME) Java1.3 2000.05.08 JDK1.3 新增JNDI和动态代理，提升企业集成能力 Java1.4 2002.02.13 JDK1.4 引入断言（Assertions）、正则表达式和NIO Java5.0 2004.09.30 JDK1.8 更名为Java SE 5.0，支持泛型、枚举、自动装箱等 Java6 2006.12.11 JDK1.6 改进JVM性能，支持脚本语言 Java7 2011.07.28 JDK1.7 新增字符串Switch、二进制字面量等语法糖 Java8 2014.03.18 JDK8 里程碑版本，引入Lambda表达式、Stream API（长期支持版LTS） Java9 2017.09.21 JDK9 模块化系统（JPMS），每半年发布一次版本 Java10 2018.03.20 JDK10 Java11 2018.09.25 JDK11 移除Java EE模块，支持HTTP Client（LTS） Java12 2019.03.19 JDK12 Java13 2019.09.17 JDK13 Java14 2020.03.17 JDK14 Java15 2020.09.16 JDK15 Java16 2021.03.16 JDK16 Java17 2021.09.14 JDK17 密封类（Sealed Classes）、模式匹配等（LTS） Java18 2022.03.22 JDK18 Java19 2022.09.20 JDK19 Java20 2023.03.21 JDK20 Java21 2023.09.19 JDK21 虚拟线程（Loom）、分代ZGC（LTS） Java22 2024.03.19 JDK22 Java23 2024.09.17 JDK23 Java24 2025.03.18 JDK24 Java 24 是自 Java 21 后的第三个非长期支持版本 2.选择并安装JDK 在Oracle官网下载JDK，有很多版本可供选择，建议选择Oracle官方的长期支持版，如：JDK8、JDK11、JDK17、JDK21，这里选用JDK21作为示例，因为Spring官方推荐的安装环境需要JDK17或JDK21。 另外如果是下载JDK8及其以前的版本，还需要注意对应操作系统是32位或者64位，如下： 自2023年9月通过JEP 449提案，JDK21正式废弃Windows 32位x86平台的构建支持，但仍允许通过特定配置编译32位版本。本文以 Window11 64位系统为例，如下： 下载完成jdk-21_windows-x64_bin.exe或者jdk-21_windows-x64_bin.msi文件，双击运行进行安装，中间无需调整安装选项。安装完成后可以在命令行输入命令 java -version 查看系统中安装的JDK版本，如下： 二、配置环境变量（JAVA_HOME） 从JDK11开始，安装后默认无需手动配置 Path 变量即可使用基础命令，但 JAVA_HOME 仍需按需设置。对于开发场景，建议始终补充 JAVA_HOME 以规避兼容性问题。 1.JDK安装完成后，从Windows开始菜单打开 设置 ，进入 系统 中的 系统信息 ，点击 高级系统设置 ； 2.选择 高级 选项卡，点击 环境变量 ； 然后就会出现 环境变量 设置界面，如下： 3.在系统变量中设置3个变量： JAVA_HOME 、 PATH 、 CLASSPATH ，若已存在则点击 编辑 ，不存在则点击 新建 。 注意：使用JDK5以上的版本，不用设置CLASSPATH环境变量，也可以正常编译和运行Java程序。 变量设置的相关值如下： 变量名：JAVA_HOME 变量值：C:\Program Files\Java\jdk-21 // 要根据自己的实际安装路径配置 变量名：CLASSPATH 变量值：.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; // 注意前面有个英文句号"." 变量名：Path 变量值：%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; // JDK9开始取消了jre目录 注意：从Windows10开始，PATH变量是按不同路径分隔的条目，需要将 %JAVA_HOME%\bin 和 %JAVA_HOME%\jre\bin 逐条添加，如下： 三、测试JDK 配置好系统环境变量之后就可以使用JDK提供的命令编译、运行Java程序。这里运行一个简单的Java程序对安装好JDK后的开发环境进行测试。 1.编写HelloWorld.java文件 在系统主用户目录下新建HelloWorld.java文件（可以新建文件后使用记事本进行编辑），代码如下： public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } } 2.编译HelloWorld.java文件 在命令行中进入HelloWorld.java文件所在的目录，使用javac命令编译java文件。 javac HelloWorld.java HelloWorld.java文件经过javac编译后会得到HelloWorld.class文件，如下： 3.运行HelloWorld 在命令行中进入HelloWorld.class文件所在的目录，使用java命令运行HelloWorld程序。 java HelloWorld // 注意不要带.class文件后缀名 运行成功就会在命令行中输出“Hello World”，如下： 四、选择流行的Java开发工具软件 Java程序的编写、编译、运行肯定不能只依赖记事本、命令行，子曰：“工欲善其事，必先利其器”，目前市场上有许多流行的IDE（IDE，Integrated Development Environment，集成开发环境）软件可以选择，下面介绍几款常用的IDE软件。 1.Eclipse Eclipse是一款开源、跨平台的IDE，最初由IBM于2001年贡献给开源社区，现由Eclipse基金会维护。它以插件化架构为核心，支持通过扩展插件适应多种编程语言和开发场景，是开发者构建复杂软件系统的首选工具。下载地址：https://www.eclipse.org/downloads/packages/ 下载地址中的Eclipse IDE for Java Developers和Eclipse IDE for Enterprise Java and Web Developers都可以开发Java程序，但两者有不同的定位和使用场景，如下： Eclipse IDE for Java Developers 定位：轻量级基础开发工具，适合 个人开发者或小型项目。 适用场景： 纯 Java SE 应用开发（如桌面程序、命令行工具）。 无需 Web 或企业级功能（如 JSP、数据库连接）。 开发工具链需求简单（仅需基础 Java IDE、Git、Maven/Gradle）。 Eclipse IDE for Enterprise Java and Web Developers 定位：企业级全栈开发工具，适合 Java EE、Web 应用及分布式系统。 适用场景： 开发企业级 Java EE 应用（如使用 JPA、JSF、EJB）。 Web 开发（HTML、JSP、Servlet、JavaScript）。 集成数据库工具、应用服务器（如 Tomcat、GlassFish）。 为了方便后续学习Java EE、Web应用的开发，可以选择Eclipse IDE for Enterprise Java and Web Developers下载安装，如下： 2.JetBrains的IDEA JetBrains的IntelliJ IDEA是一款以智能化和高效著称的IDE，专注于Java/Kotlin开发，同时通过插件支持多种编程语言和框架。下载地址：https://www.jetbrains.com/idea/download/ 下载地址中的IntelliJ IDEA Ultimate（旗舰版）和IntelliJ IDEA Community（社区版）都可以开发Java程序，但两者有不同的定位和使用场景，如下： IntelliJ IDEA Ultimate（旗舰版） 适用场景：企业级开发（如 Spring、微服务、数据库集成）、全栈开发（支持前端框架如 React/Vue）。 核心功能：深度支持 Java EE、Spring Boot、数据库工具、Docker/Kubernetes 集成等。 授权模式：付费订阅（提供 30 天试用，学生可免费申请授权）。 IntelliJ IDEA Community（社区版） 适用场景：学习、基础 Java/Kotlin 开发（如 Android 应用、小型项目）。 功能限制：缺少企业级框架支持（如 Spring）、数据库工具等高级功能。 授权模式：免费开源。 为了方便后续学习Java EE、Web应用的开发，可以选择IntelliJ IDEA Ultimate（旗舰版）下载安装，如下： 3.Visual Studio Code Visual Studio Code（简称VS Code）是一款由微软开发的免费开源、跨平台的现代化代码编辑器，自2015年发布以来已成为全球开发者首选的开发工具之一。下载地址：https://code.visualstudio.com/ 下载地址中的Visual Studio Code需要通过插件Extension Pack for Java才能支持Java程序开发，如下： 4.三种IDE的对比 特性 IntelliJ IDEA Eclipse VS Code 启动速度 中等 较慢 快 智能代码辅助 强（原生） 弱（依赖插件） 中（需插件） 企业级支持 完善（旗舰版） 需手动配置 需扩展配置 资源占用 较高 中等 低
一、Java语言的起源与设计理念 Java由詹姆斯·高斯林（James Gosling）领导的团队于1991年在Sun Microsystems启动开发，最初用于嵌入式家电设备（如电视机顶盒）的编程。1995年正式发布后，凭借“一次编写，到处运行”（WORA）的跨平台特性迅速成为互联网时代的核心语言。其设计初衷是简化C++的复杂性，摒弃多继承、指针等易错概念，同时强化面向对象特性和安全性。Oracle于2010年收购Sun后，持续推动Java的技术迭代与生态扩展。 二、Java的核心特性 跨平台性与JVM机制 Java程序通过编译器生成字节码（.class文件），由Java虚拟机（JVM）解释执行。JVM为不同操作系统提供统一运行环境，实现“代码一次编写，多平台运行”。例如，Windows编译的字节码可直接在Linux或MacOS的JVM上运行，极大降低开发成本。 纯粹的面向对象设计 Java强制使用类与对象结构，支持封装、继承、多态三大特性： 封装：通过私有变量（private）与公共方法（getter/setter）保护数据完整性。 继承：子类复用父类代码（如Animal类派生出Dog/Cat类）。 多态：接口（如Shape）允许多种实现（Circle/Rectangle），增强代码灵活性。 内存管理与安全性 垃圾回收（GC）：自动回收无用对象内存，避免内存泄漏。 安全机制：字节码验证、安全管理器（Security Manager）及沙箱模型限制恶意代码权限，防止系统攻击。 高性能与并发支持 即时编译（JIT）：将高频代码编译为机器码，提升执行效率。 多线程：内置Thread类与synchronized关键字简化并发编程，适用于服务器端高并发场景。 丰富的API与生态系统 提供超过4500个核心类库，涵盖网络（java.net）、IO流（java.io）、集合框架（java.util）等模块。Spring、Hibernate等开源框架进一步扩展了企业级开发能力。 三、Java的发展历程与版本迭代 1995-2000年：Java 1.0发布，引入Applet（网页小程序）；Java 2平台划分为J2SE（标准版）、J2EE（企业版）和J2ME（微型版）。 2004年：Java 5引入泛型、自动装箱/拆箱、注解（Annotation）等，提升代码简洁性。 2014年：Java 8推出Lambda表达式与Stream API，支持函数式编程，简化集合操作。 2021年：Java 17成为长期支持（LTS）版本，新增密封类（sealed class）、模式匹配等特性，强化代码可维护性。 四、Java的应用场景 企业级应用开发 占全球企业级市场60%以上份额，Spring框架与J2EE规范支撑银行系统（如支付清算）、ERP等复杂业务。 移动开发 Android平台以Java为核心开发语言，支付宝、微信等应用均依赖Java处理后台逻辑。 大数据与云计算 Hadoop、Spark等大数据框架基于Java构建；云原生技术（如Kubernetes）依赖Java的高可扩展性。 Web开发 通过Servlet、JSP和Spring Boot构建动态网站，典型案例包括LinkedIn、eBay等。 嵌入式与物联网（IoT） 早期Oak语言设计为智能家电赋能，如今在车载系统、智能家居中仍有应用。 五、未来趋势与挑战 云原生与微服务：Quarkus、Micronaut等轻量级框架优化Java在容器化环境中的启动速度与内存占用。 新语言特性：Project Loom引入虚拟线程（Virtual Threads），提升高并发性能；Project Valhalla探索值类型（Value Types）以优化内存使用。 竞争与替代：Kotlin（Android官方推荐语言）、Go等新兴语言对Java构成挑战，但其庞大的开发者社区与成熟生态仍具不可替代性。 结语 Java凭借跨平台、高性能、强生态等优势，历经30年发展仍稳居TIOBE指数前三。从嵌入式系统到云计算，其“一次编写，到处运行”的理念持续推动技术创新。未来，Java将继续通过版本迭代拥抱现代化编程范式，巩固其作为“编程界常青树”的地位。
如果您看到这篇文章,表示您的 blog 已经安装成功.