Java 基础 -- Java 基础知识
原文链接:Java 基础 – Java 基础知识
原为作者:弗锤
公众号:弗锤
目 录
- Java 基础知识
- 一、Java 概述
- 1.1 Java 简介
- 1.1.1 Java 介绍
- 1.1.2 Java 核心特性
- 1.1.3 Java 技术体系
- 1.2 Java 开发环境
- 1.2.1 Java 开发环境介绍
- 1.2.2 JDK 下载
- 1.2.3 Windows 免安装版
- 1.3 Java 开发工具
- 二、Java 语言基础
- 2.1 Java 程序入口点
- 2.2 Java 基本语法
- 2.3 Java 基本数据类型
- 2.3.1 基本数据类型介绍
- 2.3.2 数据类型转换
- 2.3.3 Java 包装类
- 2.4 Java 变量与常量
- 2.4.1 变量与常量介绍
- 2.4.2 Java 变量
- 2.5.3 Java 常量
- 2.5 Java 修饰符
- 2.5.1 Java 修饰符介绍
- 2.5.2 访问控制修饰符
- 2.5.3 非访问修饰符
- 2.6 Java 运算符
- 2.6.1 算术运算符
- 2.6.2 关系运算符
- 2.6.3 位运算符
- 2.6.4 逻辑运算符
- 2.6.5 赋值运算符
- 2.6.6 其他运算符
- 三、Java 流程控制
- 3.1 Java 条件语句
- 3.1.1 if 条件语句
- 3.1.2 switch 分支语句
- 3.2 Java 循环语句
- 3.2.1 for 循环语句
- 3.2.2 while 循环语句
- 3.2.3 do...while 循环语句
- 3.3 Java 循环控制
- 3.3.1 break 语句
- 3.3.2 continue 语句
- 四、Java 数组
- 4.1 数组概述
- 4.2 一维数组
- 4.2 二维数组
- 五、Java 常用类
- 5.1 Number 类
- 5.1.1 Number 类介绍
- 5.1.2 Number 类的子类
- 5.2 Math 类
- 5.2.1 Math 类介绍
- 5.2.2 Math 类常用方法
- 5.3 Character 类
- 5.3.1 Character 类介绍
- 5.3.2 Character 类判断方法
- 5.3.3 Character 类字符转换方法
- 5.3.4 Character 类应用示例
- 5.4 String 类
- 5.4.1 String 类介绍
- 5.4.2 String 类常用方法
- 5.4.3 String 类的不可变性
- 5.4.3 String 类不可变的实现原理
- 5.4.4 字符串常量池
- 5.5 StringBuffer 类
- 5.5.1 StringBuffer 类介绍
- 5.5.2 StringBuffer 类常用方法
- 5.6 StringBuilder 类
- 5.6.1 StringBuilder 类介绍
- 5.6.1 StringBuilder 类常用方法
- 5.7 Scanner 类
- 5.7.1 Scanner 类介绍
- 5.7.2 Scanner 类常用方法
- 5.7.3 Scanner 类使用示例
- 5.7.4 Scanner 类使用注意
Java 基础知识
一、Java 概述
1.1 Java 简介
1.1.1 Java 介绍
Java 是一种广泛使用、功能强大、跨平台的高级编程语言和计算平台。
1.1.2 Java 核心特性
简单易学:Java 的语法基于 C/C++,但去除了 C/C++ 中复杂且容易出错的特性,如指针运算、多重继承(通过接口实现类似功能)、显式内存管理(通过垃圾回收机制)等。
面向对象(Object-Oriented - OOP):Java 是一门纯粹的面向对象编程语言(除了基本数据类型),一切皆对象(Everything is an Object)。Java 完全支持 OOP 的四大核心特性:
- 封装(Encapsulation):将数据(属性)和操作数据的方法(行为)捆绑在一起,通过访问修饰符(private,public,protected)控制访问权限。
- 继承(Inheritance):子类可以继承父类的属性和方法,实现代码复用。
- 多态(Polymorphism):同一个接口可以有不同的实现方式(如方法重写 @Override),提高了代码的灵活性和可扩展性。
- 抽象(Abstraction):通过抽象类和接口定义规范,隐藏复杂的实现细节。
平台无关性:“一次编写,到处运行”(Write Once,Run Anywhere - WORA),这也是 Java 最核心、最革命性的特性。
- 原理:Java 源代码(.java 文件)首先被编译成一种与平台无关的中间代码——字节码(Bytecode)(.class 文件)。然后,这个字节码由安装在目标操作系统上的 Java 虚拟机(Java Virtual Machine - JVM) 来解释执行或即时编译(JIT)成本地机器码。
- 结果:只要目标机器安装了相应版本的 JVM,同一个编译好的 .class 文件就可以在 Windows、Linux、macOS 等不同操作系统上运行,无需重新编译。这极大地提高了软件的可移植性。
健壮性:Java 通过多种机制确保程序的稳定性和可靠性:
- 强类型检查(Strong Typing):在编译和运行时进行严格的类型检查,减少类型错误。
- 异常处理(Exception Handling):提供了完善的异常处理机制(try-catch-finally,throw,throws),允许程序优雅地处理运行时错误,避免程序崩溃。
- 自动内存管理 / 垃圾回收(Garbage Collection - GC):JVM 自动管理内存的分配和回收,程序员无需手动释放内存(如 C/C++ 中的 free/delete),有效避免了内存泄漏(Memory Leak)和悬空指针(Dangling Pointer)等常见问题。
安全性:Java 设计之初就考虑了安全性,尤其是在网络环境下:
- 沙箱机制(Sandbox):限制不受信任代码(如早期的 Applet)的权限,防止其访问本地文件系统、网络等敏感资源。
- 字节码验证器(Bytecode Verifier):在 JVM 加载字节码时进行检查,确保其符合规范,防止恶意代码破坏系统。
- 安全管理器(Security Manager):(虽然在现代应用中使用减少)可以进一步定义代码的权限。
- 加密和安全 API:提供了丰富的 API 支持加密、数字签名、认证等安全功能。
多线程:
- Java 内置了对多线程编程的支持。一个 Java 程序可以同时运行多个执行路径(线程)。这使得开发高并发、响应迅速的应用(如服务器、图形界面程序)变得更加容易。
- Java 提供了 Thread 类、Runnable 接口以及 java.util.concurrent 包等工具来简化多线程开发。
分布式:Java 为网络编程提供了强大的内置支持(如 java.net 包),可以轻松地开发能够通过网络进行通信和数据交换的应用程序,如客户端-服务器(Client-Server) 应用、Web 服务等。
高性能:
- 虽然早期的解释执行模式较慢,但现代 JVM 采用了即时编译(Just-In-Time Compilation - JIT) 技术。
- JIT 会将热点(频繁执行)的字节码编译成本地机器码,从而获得接近 C/C++ 的执行速度。加上不断优化的垃圾回收算法,Java 的性能已经非常出色。
动态性:
- Java 能够适应不断变化的环境,其库可以自由地添加新方法和实例变量,而不会影响其客户端。
- Java 还支持反射(Reflection) 机制,允许程序在运行时检查类、接口、字段和方法的信息,并能动态调用对象的方法或访问其属性,这为框架(如 Spring)的实现提供了基础。
1.1.3 Java 技术体系
Java 的技术生态系统,主要分为三个平台:
- Java SE:Standard Edition - 标准版
- Java EE:Enterprise Edition - 企业版
- Java ME:Micro Edition - 微型版
Java SE:
- 核心:Java 的基础和核心,包含语言基础、核心 API(如
java.lang,java.util
,java.io
,java.net
,java.sql
等)、JVM。 - 用途:用于开发桌面应用程序、命令行工具以及作为 Java EE 和 Java ME 的基础。
Java EE:
- 核心:建立在 Java SE 之上,提供了一套用于开发大型、分布式、多层企业级应用的 API 和运行时环境。
- 关键组件/技术:
Servlet
,JSP(JavaServer Pages)
,EJB(Enterprise JavaBeans)
,JPA(Java Persistence API)
,JMS(Java Message Service)
,Web Services(JAX-WS,JAX-RS)
等。 - 应用服务器:需要部署在应用服务器上运行,如 Tomcat(主要支持
Servlet/JSP
),WildFly,WebLogic,WebSphere。 - 现状:Java EE 已被捐赠给 Eclipse 基金会,并更名为 Jakarta EE。
Java ME:
- 核心:为资源受限的设备(如早期的手机、PDA、嵌入式设备、物联网设备)设计的精简版 Java 平台。
- 用途:开发运行在小型设备上的应用程序。随着 Android 的崛起,其在手机领域的应用已大大减少,但在特定嵌入式领域仍有应用。
1.2 Java 开发环境
1.2.1 Java 开发环境介绍
JVM:JVM(Java Virtual Machine - Java 虚拟机)是 Java 平台无关性(“一次编写,到处运行”)的基石和执行引擎。可以把它看作是 JVM 的“运行套装”。
JRE:JRE(Java Runtime Environment - Java 运行时环境)是运行 Java 程序所必需的最小环境。
JDK:JDK(Java Development Kit - Java 开发工具包)是用于开发 Java 应用程序的完整工具包,它是功能最全的“开发套件”。
JDK、JRE、JVM 共同构成了 Java 的开发和运行环境,三者关系如下:
1.2.2 JDK 下载
JDK 的下载可以通过但不限于以下途径:
- Oracle 官网:Oracle 是 JDK 的官方发布者,提供最新版本的 JDK。
- OpenJDK 官网:OpenJDK 是开源的 JDK 实现,适合大多数开发场景。
- Adoptium 官网:原 AdoptOpenJDK,由 Eclipse 基金会维护,提供高质量的 OpenJDK 构建版本。
- Dragonwell 官网:Dragonwell 是阿里巴巴提供的国产优化版,阿里巴巴推出的长期支持的 OpenJDK 发行版,针对云环境进行了性能优化。
Java 环境变量配置,主要是配置:
JAVA_HOME
:指向 JDK 的安装目录,其他工具(如 Maven、Tomcat)会依赖此变量。- CLASSPATH:Java 类文件(
.class
)的搜索路径,现代 JDK 通常可省略此配置。 - PATH:系统可执行命令的搜索路径,便于在命令行中直接使用
java
、javac
等命令。
注意:
CLASSPATH
在JDK 1.5
以后已默认包含当前目录(.
),大多数情况下无需手动设置。tools.jar
和dt.jar
在JDK 9+
模块化后已被移除或整合,因此高版本 JDK 不需要这些。
1.2.3 Windows 免安装版
下载免安装版的 JDK,解压并放置到指定想要安装的目录,如 C:\Program Files (x86)\Java\jdk1.8.0_91
。
配置环境变量,右击“我的电脑”→“属性”→“高级系统设置”→“环境变量”。
设置 JAVA_HOME
,在“系统变量”中点击“新建”:
- 变量名:
JAVA_HOME
- 变量值:
C:\Program Files (x86)\Java\jdk1.8.0_91
,这个目录需要根据实际路径修改
在“系统变量”中找到 Path
,点击“编辑”→“新建”
- 添加:
%JAVA_HOME%\bin
设置 CLASSPATH
,在“系统变量”中点击“新建”:
- 变量名:
CLASSPATH
- 变量值:
.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar
JDK 环境变量配置的验证,打开命令提示符(cmd)输入以下命令验证,如果显示版本信息和正确路径,说明配置成功。
java -version
javac -version
echo %JAVA_HOME%
1.3 Java 开发工具
开发 Java 语言常用的开发工具如下:
- IntelliJ IDEA:目前比较主流的 Java 开发 IDE,由 JetBrain 公司提供。
- Eclipse:一款开源免费的开发工具,界面略显陈旧,配置相对比较复杂。
- Visual Studio Code:(VS Code),通过添加 Java 插件的方式满足 Java 语言开发。
- NetBeans:Apache 维护的开源 IDE,对 Java EE(Jakarta EE)支持良好,但是使用人数较少,适合特定场景。
IntelliJ IDEA 常用插件:
Alibaba Java Coding Guidelines
:阿里规约,检测代码是否符合《阿里巴巴 Java 开发手册》规范Lombok Plugin
:支持 Lombok 注解(@Data、@Getter、@Setter 等),减少模板代码,需在pom.xml
中引入 Lombok 依赖。MyBatisX:MyBatis
/MyBatis-Plus
映射跳转、XML 与接口方法互查。Spring Boot Helper
/Spring Assistant
:快速生成 Spring Boot 项目结构、配置提示、端点检测。Rainbow Brackets
:为不同层级的括号添加颜色,提升代码可读性,尤其适合嵌套复杂的表达式。Translation
:官方翻译插件,直接翻译类名、变量名、文档注释。支持谷歌、有道、百度等翻译。GitToolBox
/Git Commit Template
:增强 Git 提交信息提示、自动补全、提交模板。GenerateAllSetter
:右键对象可一键调用.setXXX()
、.setYYY()
链式赋值。Database Tools and SQL
:内置功能,支持连接 MySQL、Oracle、PostgreSQL 等数据库,可直接执行 SQL、查看表结构,无需切换工具。Maven Helper
:分析依赖冲突、可视化pom.xml
依赖树,解决 jar 包冲突利器。Grep Console
:针对控制台日志,设置不同级别日志的字体颜色和背景色。CodeGlance
:CodeGlance 是一款代码编辑区缩略图插件,可以快速定位代码。RestfulToolkit
:RestfulToolkit 是一套 RESTful 服务开发辅助工具集。JRebel
:热部署插件,可以在修改完代码后,不用重新启动代码。Json Parser
:JSON 格式化工具。SonarLint
:代码静态分析,专注于代码质量。- AI 相关插件:
JetBrains AI Assistant
:官方提供的 AI 插件Lingma
:阿里提供通义灵码的插件CodeGeeX
:支持代码生成、翻译、解释GitHub Copilot
:代码自动补全AI Code Reviewer
:代码审查助手tabnine
:智能代码补全,AI 问答等Code Vision
:代码智能提示插件,在方法或类名上方显示 AI 分析结果,如:代码复杂度评分、潜在性能问题提示、测试覆盖率建议等。
VS Code 针对 Java 开发的常用插件:
Java Extension Pack
:官方推荐合集,包含以下核心插件,安装一个等于安装六个:Language Support for Java™ by Red Hat
Debugger for Java
Test Runner for Java
Maven for Java
Project Manager for Java
Visual Studio Code for Java
Spring Boot Extension Pack
:包含如下Spring Boot Tools
:自动补全、配置提示Spring Initializr Java Support
:快速创建项目
Lombok Annotations Support
:支持 Lombok 注解解析- Code Runner`:一键运行 Java、Python、C++ 等单文件程序
Chinese (Simplified) Language Pack
:中文语言包,界面汉化
Eclipse 常用插件:
Lombok for Eclipse
:需要手动安装,下载lombok.jar
,双击运行指定 Eclipse 安装路径。Spring Tools 4 (STS)
:官方 Spring 工具包,支持 Spring Boot 自动配置提示,可通过 Eclipse Marketplace 安装。MyBatis Generator
:自动生成 MyBatis 实体类、Mapper 接口和 XML 文件。FindBugs
/SpotBugs
:静态代码分析工具,查找潜在 bug。EGit
:Eclipse 内置 Git 客户端,方便版本控制。
二、Java 语言基础
2.1 Java 程序入口点
每个可独立运行的 Java 程序都必须包含一个 main 方法,main 方法是 Java 程序的入口点(Entry Point),JVM 在启动程序时会自动查找并执行这个方法。
main 方法如下:
public static void main(String[] args) {System.out.println("Hello, World!");
}
main 方法解释:
public
:访问修饰符,表示该方法可以被外部调用(特别是被 JVM 调用)。必须是 public,否则 JVM 无法访问。static
:静态方法,表示该方法属于类本身,而不是类的实例。这样 JVM 可以在不创建对象的情况下直接调用 main 方法。void
:返回类型,表示该方法没有返回值。main 方法不能有返回值。main
:方法名,JVM 规定的入口方法名称,不能更改。String[] args
:方法参数,用于接收命令行传递的参数(如字符串数组)。
main 方法的注意事项:
- 必须是
public static void
- 方法名必须是 main
- 参数必须是
String[]
类型 - 一个类可以有多个 main 方法(重载),但只有签名正确的才是入口
- main 方法可以抛出异常
main 方法对比:
public static void main(String[] args) { ... } // ✅ 入口
public static void main() { ... } // ❌ 不是入口
public static void main(String args) { ... } // ❌ 参数类型不对
main 方法抛出异常:
public static void main(String[] args) throws Exception {// 可能出错的操作
}
2.2 Java 基本语法
Java 语法的基本规则:
-
大小写敏感:Java 是大小写敏感的语言,HelloWorld 和 helloworld 是两个不同的标识符,类名、方法名、变量名都区分大小写。
-
类名命名规则:首字母大写,采用大驼峰命名法(PascalCase),如
MyClass
,StudentInfo
,BankAccount
。 -
方法名命名规则:首字母小写,后续单词首字母大写(小驼峰命名法 camelCase)如
main()
,handleUserInfo()
,getUserName()
。 -
代码块:代码块使用花括号
{}
。 -
代码语句:每条语句以分号
;
结束。 -
变量名和函数参数:只能包含字母、数字、
_
、$
,必须以字母、$
或_
开头,不能以数字开头。`推荐使用有意义的名称,采用小驼峰 -
源文件名:源文件名必须和类名相同。当保存文件的时候,应该使用类名作为文件名保存,文件名的后缀为
.java
,如果文件名和类名不相同则会导致编译错误。 -
代码注释:
- 单行注释:使用
//
,注释单行代码 - 多行注释:使用
/* 多行<br>注释 */
,注释多行代码 - 文档注释:使用
/** JavaDoc 注释 */
,常用于生成 API 文档,可被 javadoc 工具解析
- 单行注释:使用
注释示例:
// 这是单行注释,可以注释单行
int x = 10; // 行内的单行注释/*
这是多行注释
可以注释多行*//*** 这是文档注释* 也可以注释多行* 常用于生成 API 文档*/
2.3 Java 基本数据类型
2.3.1 基本数据类型介绍
Java 中的基本数据类型分为四大类:整数型、浮点型、字符型、布尔型。其中:
- 整数型包括字节型、短整型、整形和长整型
- 浮点型包括单精度和双精度
基本数据类型对比如下:
分类 | 数据类型 | 关键字 | 占用空间 | 取值范围 | 默认值 |
---|---|---|---|---|---|
整数型 | 字节型 | byte | 1 字节(8 位) | -128 到 127 | 0 |
短整型 | short | 2 字节(16 位) | -32,768 到 32,767 | 0 | |
整型 | int | 4 字节(32 位) | -2³¹ 到 2³¹-1(约 -21 亿 ~ 21 亿) | 0 | |
长整型 | long | 8 字节(64 位) | -2⁶³ 到 2⁶³-1 | 0L | |
浮点型 | 单精度 | float | 4 字节(32 位) | 约 ±3.4×10³⁸(6-7 位有效数字) | 0.0f |
双精度 | double | 8 字节(64 位) | 约 ±1.8×10³⁰⁸(15 位有效数字) | 0.0d | |
字符型 | 字符 | char | 2 字节(16 位) | 0 到 65,535(Unicode 字符) | \u0000 |
布尔型 | 布尔 | boolean | 未定义(通常 1 位) | true 或 false | false |
整数型:
- 用于表示没有小数部分的数字。
long
类型字面量必须加L
,否则会被当作int
。
浮点型:
- 浮点数默认是
double
类型,赋值给float
必须加f
。 - 不要用
float
/double
表示金钱!因为会有精度丢失问题。
字符型 char
- 表示单个字符,使用单引号
' '
。
布尔型 boolean
- 表示逻辑值,只有两个值:
true
和false
。 boolean
类型在 JVM 中实际占用空间由虚拟机实现决定,但逻辑上只表示真假。
2.3.2 数据类型转换
Java 中各种数据类型间可以进行互相转换:
- 从小类型自动转为大类型为自动类型转换,也叫隐式转换。如
byte → short → int → long → float → double
。 - 从大类型转小类型为强制类型转换,也叫显式转换,这种转换可能会丢失精度。如
double → float → long → int → short → byte
。
自动类型转换示例:
int a = 100;
long b = a; // ✅ int → long(安全)float f = 3.14f;
double d = f; // ✅ float → double
强制类型转换示例:
long l = 1000L;
int i = (int) l; // ✅ 强制转换double d = 3.99;
int n = (int) d; // 结果是 3(直接截断,不四舍五入)
2.3.3 Java 包装类
在 Java 中,基本数据类型(Primitive Data Types) 和 包装类(Wrapper Classes) 是两种表示数据的方式。
为了使基本类型能参与面向对象操作(如放入集合、泛型),Java 为每种基本类型提供了对应的包装类,它们是对象,位于 java.lang
包中。
基本类型 | 包装类 | 对象示例 |
---|---|---|
byte | Byte | Byte b = 10; |
short | Short | Short s = 20; |
int | Integer | Integer i = 100; |
long | Long | Long l = 1000L; |
float | Float | Float f = 3.14f; |
double | Double | Double d = 3.14159; |
char | Character | Character c = ‘X’; |
boolean | Boolean | Boolean flag = true; |
自动装箱与拆箱:
- 自动装箱(Autoboxing):将基本类型自动转换为对应的包装类对象。
- 自动拆箱(Unboxing):将包装类对象自动转换为基本类型。
2.4 Java 变量与常量
2.4.1 变量与常量介绍
在 Java 编程中,变量(Variable)和常量(Constant)是存储数据的基本方式。
- 变量:变量是程序中用于存储数据值的容器,它的值在程序运行过程中可以被修改。
- 常量:常量是在程序运行期间其值不能被改变的量。
2.4.2 Java 变量
变量的声明与赋值语法:
// 先声明变量,再给变量赋值
数据类型 变量名;
变量名 = 值;// 声明变量的同时给变量赋值
数据类型 变量名 = 值;
变量示例:
int age; // 声明变量
age = 25; // 给变量赋值double price = 9.99; // 声明变量时初始化值
String name = "张三"; // 字符串变量声明并赋值
变量的命名规则
- 只能包含:字母、数字、
_
、$
- 不能以数字开头:如
1age
- 区分大小写:
age
和Age
是两个变量 - 不能是 Java 关键字:如
int class = 10
- 推荐使用小驼峰命名法(camelCase):
userName
,studentInfo
变量的作用域:变量根据定义位置不同,其作用域(Scope)也不同
- 局部变量:定义在方法内部,仅在该方法内有效,使用前必须先初始化
- 成员变量:也叫实例变量,定义在类中、方法外,成员变量属于对象,在整个类都可访问,有默认值。
- 静态变量:也叫类变量,用 static 修饰,静态变量属于类,是所有实例共享的。
变量的作用域示例:
public class Student {// 成员变量,默认 null,当前类可访问private String name;// 静态变量,当前类可访问private static String CLASS_NUM = "一班";public void study() {// 局部变量,必须初始化,当前方法可访问int count = 5;}
}
局部变量(方法内的变量)没有默认值,必须手动初始化才能使用,如下:
public void test() {int x;System.out.println(x); // ❌ 编译错误:变量未初始化
}
2.5.3 Java 常量
常量使用 final
关键字修饰,变量名通常使用全大写字母,单词间用下划线 _
分隔。变量在声明时就必须初始化。
常量声明语法如下:
final 数据类型 常量名 = 值;
常量示例:
final int MAX_AGE = 120;
final double PI = 3.1415926;
final String NAME = "张三";
常量的好处
- 防止误修改:避免程序中意外更改关键数值
- 提高可读性:
PI
比 3.1415926 更易理解 - 便于维护:如果需要修改,只需改一处
2.5 Java 修饰符
2.5.1 Java 修饰符介绍
修饰符用来定义类、方法、变量等的关键词,用于控制访问权限、行为特性和生命周期。通常放在语句的最前端。
Java 语言提供了很多修饰符,主要分为以下两类:
- 访问修饰符
- 非访问修饰符
2.5.2 访问控制修饰符
访问控制修饰符用来保护对类、变量、方法和构造方法的访问,Java 支持 4 种不同的访问权限:
default
:默认的修饰符,在同一包内可见。可用于修饰类、接口、变量、方法。private
:在同一类内可见。可用于修饰变量、方法。private
不能修饰外部类。public
:对所有类可见。可用于修饰类、接口、变量、方法。protected
:对同一包内的类和所有子类可见。可以用修饰变量、方法。protected
不能修饰外部类。
访问修饰符的访问权限对比:
修饰符 | 当前类 | 同一包内 | 同包子孙类 | 不同包子孙类 | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N(通过继承) | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
protected 说明:
- 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
- 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的 protected 方法。
访问控制权限和继承:
- 父类中声明为
public
的方法在子类中也必须为public
。 - 父类中声明为
protected
的方法在子类中要么声明为protected
,要么声明为public
,不能声明为private
。 - 父类中声明为
private
的方法,不能够被子类继承。
2.5.3 非访问修饰符
非访问修饰符用于定义类、方法、变量的行为特性,Java 提供的常用非访问修饰符如下:
static
final
abstract
synchronized
volatile
transient
native
static
修饰符:
static
用来修饰类方法、类变量和代码块:
- 修饰变量时:该变量属于类变量,所有实例共享
- 修饰方法时:该方法属于类方法,不依赖对象即可调用
- 修饰代码块时:该代码块只会在类加载时执行一次
static
示例:
public class StaticDemo {// 静态变量,所有对象共享static int count = 0;// 静态方法static void handleCount() {System.out.println("Count: " + count);}// 静态代码块static {System.out.println("静态代码块,类加载时执行");}
}// 其他类调用 displayCount 方法时无需创建对象
Counter.displayCount();
final
修饰符用来修饰类、方法和变量:
final
修饰类时,该类不能够被继承。String
类是final
的,不能被继承final
修饰方法时,该方法不能被子类重写(override)final
修饰变量时,即为常量,是不可修改的。
final
示例:
// 该类不能被继承
final class MathUtils {// 常量final double PI = 3.14159;// 该方法不能被重写final void printInfo() {System.out.println("工具类");}
}
abstract
修饰符用来创建抽象类和抽象方法。
abstract
修饰类时,该类不能被实例化,必须被继承abstract
修饰方法时:该方法只有声明,没有实现,子类继承时实现
abstract
示例:
abstract class Animal {// 抽象方法,无方法体,由子类实现abstract void makeSound();// 普通方法可以有实现void sleep() {System.out.println("Animal is sleeping");}
}class Dog extends Animal {// 必须重写抽象方法void makeSound() {System.out.println("Woof!");}
}
synchronized
用来修饰方法或代码块,保证线程安全,同一时间只有一个线程能执行。
synchronized
示例:
public synchronized void withdraw(double amount) {// 银行取款操作,防止多线程并发问题
}
volatile
用于修饰变量,确保多线程环境下变量的可见性(立即写回主内存)
volatile
示例:
private volatile boolean running = true;public void stop() {running = false;
}
transient
用于修饰变量,表示该变量不参与序列化。
transient
示例:
class User implements Serializable {String name;transient String password; // 序列化时忽略密码
}
native
用于修饰方法,表示该方法由其他语言(如 C/C++)实现,通常用于调用操作系统底层功能。
native
示例:
public native void startThread();
2.6 Java 运算符
Java 提供了一套丰富的运算符来操纵变量,主要如下:
- 算术运算符
- 关系运算符
- 位运算符
- 逻辑运算符
- 赋值运算符
- 其他运算符
2.6.1 算术运算符
Java 提供的算术运算符如下:
+
:加法-
:减法*
:乘法/
:除法%
:取模(求余数)++
:自增--
:自减
算术运算符示例:
int a = 20;
int b = 10;
int c = a + b;
int d = a - b;
int e = a * b;
int f = a / b;
int g = a % b;
int h = a++;
int i = ++a;
int j = a--;
int k = --a;
a++
与 ++a
,和 a--
与 --a
:
a++
与++a
都表示将变量 a 的值加 1,但执行时机和返回值不同。a++
是先使用原值,再加 1;++a
先加 1,再使用新值
int a = 20;
System.out.println("a=" + a++); // 20
System.out.println("a=" + a); // 21
int b = 20;
System.out.println("b=" + ++b); // 21
System.out.println("b=" + b); // 21
a--
与 --a
的逻辑和 a++
与 ++a
一样。
2.6.2 关系运算符
Java 提供的关系运算符如下:
==
:等于!=
:不等于>
:大于<
:小于>=
:大于等于<=
:小于等于
关系运算符示例:
System.out.println(1 == 1); // true
System.out.println(1 != 1); // false
System.out.println(1 > 1); // false
System.out.println(1 < 1); // false
System.out.println(1 >= 1); // true
System.out.println(1 <= 1); // true
2.6.3 位运算符
位运算符(Bitwise Operators)是 Java 中用于对整数类型的二进制位进行操作的运算符。
位运算符直接操作数据的二进制表示,效率高,常用于底层编程、性能优化、加密算法、状态标志等场景。
Java 提供的位运算符如下:
&
:按位与(AND),两个位都为 1 时结果为 1|
:按位或(OR),;两个位有一个位 1 时结果为 1,否则为 0^
:按位异或(XOR),两个位不同结果为 1,相同为 0~
:按位取反(NOT):单目运算符,0 变 1,1 变 0<<
:左移:左移若干位,低位补 0>>
:右移:右移若干位,高位补符号位(正数补 0,负数补 1)>>>
:无符号右移:右移若干位,高位始终补 0
位运算符适用于:
byte
、short
、int
、long
、char
等整数类型。
位运算符示例:
// & 按位与
int a = 6 & 3; // 0110 & 0011 = 0010 → 结果是 2
// | 按位或
int a = 6 | 3; // 0110 | 0011 = 0111 → 结果是 7
// ^ 按位异或
int a = 6 ^ 3; // 0110 ^ 0011 = 0101 → 结果是 5
// ~ 按位取反
int a = ~6; // ~0110 = 1001(在 32 位中是补码表示)
// << 左移
int a = 6 << 1; // 0110 << 1 = 1100 → 结果是 12
// >> 右移
int a = 6 >> 1; // 0110 >> 1 = 0011 → 结果是 3
// >>> 无符号右移
int a = -6 >>> 1; // 高位补 0,不考虑符号
位运算注意:
- 位运算符优先级较低,常需加括号。
- 类型提升:
byte
、short
会先提升为int
再进行位运算。- 负数使用补码:Java 中所有整数以补码存储,位运算基于补码。
>>>
与>>
区别:>>
保持符号,>>>
忽略符号,高位补 0。
2.6.4 逻辑运算符
Java 提供的逻辑运算符如下:
&&
:逻辑与,条件同时满足则为 true||
:逻辑或,只要有一个条件满足则为 true!
:逻辑非,对结果取相反的值
逻辑运算符示例:
System.out.println(true && false); // false
System.out.println(true || false); // true
System.out.println(!true); // false
2.6.5 赋值运算符
赋值运算符(Assignment Operators) 用于将某个值或表达式的结果赋给一个变量。
Java 提供的赋值运算符如下:
=
:基本赋值+=
:复合赋值,加之后再赋值,等价于a = a + b
-=
:复合赋值,减之后再赋值,等价于a = a - b
*=
:复合赋值,乘之后再赋值,等价于a = a * b
/=
:复合赋值,除之后再赋值,等价于a = a / b
%=
:复合赋值,取模之后再赋值,等价于a = a % b
&=
:复合赋值,按位与之后再赋值,等价于a = a & b
|=
:复合赋值,按位或之后再赋值,等价于a = a | b
^=
:复合赋值,按位异或之后再赋值,等价于a = a ^ b
<<=
:复合赋值,左移之后再赋值,等价于a = a << b
>>=
:复合赋值,右移之后再赋值,等价于a = a >> b
>>>=
:复合赋值,无符号右移之后再赋值,等价于a = a >>> b
赋值运算符示例:
int x = 5;
// 位运算复合赋值
x &= 5; // 相当于 x = x & 5; → 5 & 5 = 5
x |= 3; // 相当于 x = x | 3; → 5 | 3 = 7
x ^= 6; // 相当于 x = x ^ 6; → 7 ^ 6 = 1
x <<= 4; // 相当于 x = x << 4; → 1 << 4 = 16
x >>= 2; // 相当于 x = x >> 2; → 16 >> 2 = 4
x >>>= 1; // 相当于 x = x >>> 1; → 4 >>> 1 = 2
2.6.6 其他运算符
Java 还提供了一些其他的运算符,常用如下:
?:
:三元运算符,根据判断条件决定取哪个值,语法:condition?value if true : value if false
instanceof
:类型比较运算符,用于操作对象实例
示例:
// 三元运算符
int a = 10, b = 5;
int result = (a > b) ? a : b; // 如果a>b返回a,否则返回b// instanceof示例
public class App {public static void main(String[] args) {System.out.println(new Object() instanceof Object); // trueSystem.out.println(new Object() instanceof String); // falseSystem.out.println(new Object() instanceof App); // false}
}
三、Java 流程控制
3.1 Java 条件语句
3.1.1 if 条件语句
if
语句用于根据条件的真假来决定是否执行代码块。
if
语法:
// if
if (条件) {// 条件为true时执行的代码
}// if-else
if (条件) {// 条件为true时执行
} else {// 条件为false时执行
}// if-else if-else
if (条件1) {// 条件1为true时执行
} else if (条件2) {// 条件2为true时执行
} else {// 所有条件都为false时执行
}
if
示例:
int score = 85;if (score >= 90) {System.out.println("优秀");
} else if (score >= 80) {System.out.println("良好");
} else if (score >= 60) {System.out.println("及格");
} else {System.out.println("不及格");
}
// 输出:良好
3.1.2 switch 分支语句
switch
语句用于基于不同的值执行不同的代码块,适合处理多分支选择。
switch
语法:
switch (表达式) {case 值1:// 执行代码break;case 值2:// 执行代码break;default:// 默认执行代码(可选)
}
switch
示例:
int day = 3;
String dayName;switch (day) {case 1:dayName = "星期一";break;case 2:dayName = "星期二";break;case 3:dayName = "星期三";break;case 4:dayName = "星期四";break;case 5:dayName = "星期五";break;default:dayName = "周末";break;
}System.out.println(dayName); // 输出:星期三
在 switch-case
语句中,如果某个 case
分支 没有 break;
语句,程序会继续执行下一个 case
或 default
分支中的代码,直到遇到 break
、return
或 switch
语句结束。这种现象叫做“穿透”
示例:
int day = 2;
switch (day) {case 1:System.out.println("星期一");case 2:System.out.println("星期二");case 3:System.out.println("星期三");case 4:System.out.println("星期四");default:System.out.println("其他");
}// 输出结果:
星期二
星期三
星期四
其他
default
分支可以不放在switch
语句的最后(不推荐这么做):
- 如果
default
是最后一个分支,可以不写break
。- 如果
default
不在最后,必须写break
,否则会继续执行后面的case
。
3.2 Java 循环语句
3.2.1 for 循环语句
for
循环用于在已知循环次数的情况下重复执行代码。
for
语法:
// 基础 for 循环
for (初始化; 条件; 更新) {// 循环体
}// 增强 for 循环:for-each
for (元素类型 变量名 : 数组或集合) {// 循环体
}
for
示例:
// 基础 for 循环示例:
for (int i = 1; i <= 5; i++) {System.out.println(i);
}// 增强 for 循环示例
String[] names = {"张三", "李四", "王五"};
for (String name : names) {System.out.println("你好," + name);
}
3.2.2 while 循环语句
while
循环在条件为 true 时重复执行代码块。
while
语法:
while (条件) {// 循环体
}
while
示例:
int i = 1;
while (i <= 5) {System.out.println(i);i++;
}
3.2.3 do…while 循环语句
do...while
循环也是在条件为 true
时重复执行代码块,但是会先执行一次循环体,然后在条件为 true
时继续执行。
do...while
语法:
do {// 循环体
} while (条件);
do...while
示例:
int j = 1;
do {System.out.println(j);j++;
} while (j <= 5);
3.3 Java 循环控制
3.3.1 break 语句
break
是 Java 中的一个控制流语句,用于提前终止某个循环或 switch
语句的执行,程序会跳转到该结构之后的下一条语句继续执行。
break
只能用于循环或switch
语句,不能在普通代码块中使用。
break
在 switch 语句中使用可以防止“穿透”,示例:
int day = 2;
switch (day) {case 1:System.out.println("星期一");break;case 2:System.out.println("星期二");break; // 执行完这里就跳出 switchcase 3:System.out.println("星期三");break;default:System.out.println("其他");
}
break
在循环中使用,用于提前退出循环,可用于 for
、while
、do-while
循环,一旦执行 break
,立即结束整个循环。
在 for
循环中使用:
for (int i = 1; i <= 10; i++) {if (i == 5) {break; // 当 i 等于 5 时,跳出循环}System.out.print(i + " ");
}// 输出:1 2 3 4
在 while
循环中使用:
int i = 1;
while (i <= 10) {if (i == 6) {break;}System.out.print(i + " ");i++;
}// 输出:1 2 3 4 5
带标签的 break
,用于从嵌套循环中直接跳出外层循环,语法如下:
labelName:
for ( ... ) {for ( ... ) {break labelName; // 跳出外层循环}
}
示例:
int[][] matrix = {{1, 2, 3},{4, 5, 6},{7, 8, 9}
};boolean found = false;
outer: // 标签
for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix[i].length; j++) {if (matrix[i][j] == 5) {System.out.println("找到 5,位置:[" + i + "][" + j + "]");found = true;break outer; // 直接跳出最外层循环}}
}if (found) {System.out.println("搜索结束。");
}// 输出结果
找到 5,位置:[1][1]
搜索结束。
3.3.2 continue 语句
continue
是 Java 中的一个控制流语句,用于在循环中跳过当前这一次迭代,直接进入下一次循环的判断和执行,不会终止整个循环。
在 for
循环中使用:
for (int i = 1; i <= 10; i++) {if (i % 2 == 0) {continue; // 如果是偶数,跳过本次循环}System.out.print(i + " ");
}// 输出结果:
1 3 5 7 9
在 while 循环中使用:
int i = 0;
while (i < 5) {i++;if (i == 3) {continue; // 跳过 i=3 时的打印}System.out.print(i + " ");
}// 输出结果:
1 2 4 5
在 do-while
循环中使用
int i = 0;
do {i++;if (i == 2) {continue;}System.out.print(i + " ");
} while (i < 3);// 输出结果:
1 3
带标签的 continue
用于在嵌套循环中,跳过外层循环的当前迭代,语法:
outer:
for ( ... ) {for ( ... ) {continue outer; // 跳过外层循环的当前迭代}
}
示例:
outer:
for (int i = 1; i <= 3; i++) {for (int j = 1; j <= 3; j++) {if (i == 2) {continue outer; // 当 i=2 时,跳过整个内层循环,直接进入 i=3}System.out.print(i + "," + j + " ");}System.out.println(); // 换行
}// 输出结果:
1,1 1,2 1,3
3,1 3,2 3,3
四、Java 数组
4.1 数组概述
数组是一种用于存储固定大小的同类型元素的数据结构,是 Java 中最基本的数据结构之一。
数组的特点:
- 存储相同类型的元素
- 长度固定,一旦创建不能改变
- 元素按顺序存储
- 通过索引访问元素,索引从 0 开始
数组分为:
- 一维数组
- 二维数组
4.2 一维数组
一维数组是最基本的数组形式,可以看作是一排连续的"格子",每个格子存储一个相同类型的数据。
语法:
// 声明方式(两种等效写法)
int[] arr; // 推荐写法
int arr[];// 创建数组(动态初始化)
arr = new int[5]; // 创建长度为5的整型数组// 声明并创建
double[] scores = new double[10];
String[] names = new String[3];
一维数组初始化示例:
// 方式1:动态初始化(系统赋予默认值)
int[] arr1 = new int[5]; // {0, 0, 0, 0, 0}// 方式2:静态初始化(手动赋值)
int[] arr2 = new int[]{1, 2, 3, 4, 5};// 方式3:简化静态初始化(最常用)
int[] arr3 = {1, 2, 3, 4, 5};
String[] fruits = {"苹果", "香蕉", "橙子"};
数组使用示例:
public static void main(String[] args) {// 创建并初始化数组int[] numbers = {10, 20, 30, 40, 50};// 获取数组长度System.out.println("数组长度:" + numbers.length); // 5// 访问和修改元素System.out.println("第一个元素:" + numbers[0]); // 10numbers[1] = 25; // 修改第二个元素// 遍历数组System.out.println("遍历数组:");for (int i = 0; i < numbers.length; i++) {System.out.println("numbers[" + i + "] = " + numbers[i]);}// 增强for循环遍历System.out.println("增强for循环:");for (int num : numbers) {System.out.print(num + " ");}System.out.println();
}
4.2 二维数组
二维数组可以看作是"数组的数组",形成一个表格结构,有行和列两个维度。
- 每个元素通过两个索引访问:行索引和列索引
- 可以是规则的(每行长度相同)或不规则的(锯齿数组)
语法:
// 声明
int[][] matrix;// 创建规则二维数组(3行4列)
matrix = new int[3][4];// 声明并创建
double[][] table = new double[2][3];
String[][] grid = new String[3][3];
二维数组初始化示例:
// 方式1:动态初始化(默认值)
int[][] arr1 = new int[3][3]; // 全部为0// 方式2:静态初始化
int[][] arr2 = new int[][]{{1, 2, 3},{4, 5, 6},{7, 8, 9}
};// 方式3:简化静态初始化
int[][] matrix = {{1, 2, 3},{4, 5, 6},{7, 8, 9}
};// 不规则数组(锯齿数组)
int[][] jagged = {{1, 2},{3, 4, 5},{6, 7, 8, 9}
};
二维数组使用示例:
public static void main(String[] args) {// 创建并初始化二维数组int[][] matrix = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};// 获取数组维度System.out.println("行数:" + matrix.length); // 3System.out.println("第一行列数:" + matrix[0].length); // 3// 访问和修改元素System.out.println("matrix[0][0]:" + matrix[0][0]); // 1matrix[1][1] = 55; // 修改中间元素// 遍历二维数组System.out.println("遍历二维数组:");for (int i = 0; i < matrix.length; i++) {for (int j = 0; j < matrix[i].length; j++) {System.out.print(matrix[i][j] + "\t");}System.out.println(); // 换行}// 增强for循环遍历System.out.println("增强for循环遍历:");for (int[] row : matrix) {for (int element : row) {System.out.print(element + "\t");}System.out.println();}
}
五、Java 常用类
5.1 Number 类
5.1.1 Number 类介绍
Number
类是 Java 中所有"数字包装类"的抽象父类,位于 java.lang
包中,它为各种数值类型提供了统一的接口。
Number
类源码:
public abstract class Number implements java.io.Serializable {// 抽象方法,子类必须实现public abstract int intValue();public abstract long longValue();public abstract float floatValue();public abstract double doubleValue();public byte byteValue() {return (byte)intValue();}public short shortValue() {return (short)intValue();}private static final long serialVersionUID = -8742448824652078965L;
}
Number
类的方法示例:
Number num = 3.1415926;
System.out.println(num.byteValue()); // 3
System.out.println(num.shortValue()); // 3
System.out.println(num.intValue()); // 3
System.out.println(num.longValue()); // 3
System.out.println(num.floatValue()); // 3.1415926
System.out.println(num.doubleValue()); // 3.1415926
所有的包装类(Integer
、Long
、Byte
、Double
、Float
、Short
)都是抽象类 Number
的子类。
5.1.2 Number 类的子类
Byte
类源码:
public final class Byte extends Number implements Comparable<Byte> {...
}
Short
类源码:
public final class Short extends Number implements Comparable<Short> {...
}
Integer
类源码:
public final class Integer extends Number implements Comparable<Integer> {...
}
Long
类源码:
public final class Long extends Number implements Comparable<Long> {...
}
Double
类源码:
public final class Double extends Number implements Comparable<Double> {...
}
Float
类源码:
public final class Float extends Number implements Comparable<Float> {...
}
BigInteger
类源码:
public class BigInteger extends Number implements Comparable<BigInteger> {...
}
BigDecimal
类源码:
public class BigDecimal extends Number implements Comparable<BigDecimal> {...
}
5.2 Math 类
5.2.1 Math 类介绍
Math
类是 Java 中用于数学运算的工具类,位于 java.lang
包中,提供了大量的静态方法来执行基本的数学运算,如指数、对数、平方根、三角函数等。
Math
类的源码:
public final class Math {// 私有构造函数private Math() {}// 常量 Epublic static final double E = 2.7182818284590452354;// 常量 PIpublic static final double PI = 3.14159265358979323846;...
}
Math
类的特点:
final
类:不能被继承- 私有构造函数:不能被实例化
- 所有方法都是静态的:直接通过类名调用
- 包含数学常量:
Π
和e
5.2.2 Math 类常用方法
绝对值:
System.out.println(Math.abs(-5)); // 5
System.out.println(Math.abs(-3.14)); // 3.14
System.out.println(Math.abs(-0.0)); // 0.0
最大值和最小值:Math
类的最大值和最小值方法支持 int
, long
, float
, double
类型。
System.out.println(Math.max(10, 20)); // 20
System.out.println(Math.min(10, 20)); // 10
System.out.println(Math.min(10.1111, 10.101010)); // 10.10101
取整运算:
double num = 3.7;// 向下取整(返回不大于参数的最大整数)
System.out.println(Math.floor(num)); // 3.0// 向上取整(返回不小于参数的最小整数)
System.out.println(Math.ceil(num)); // 4.0// 四舍五入
System.out.println(Math.round(num)); // 4 (返回long)
System.out.println(Math.round(3.2f)); // 3 (返回int)
幂运算:
// 幂运算:a的b次方
System.out.println(Math.pow(2, 3)); // 8.0
System.out.println(Math.pow(4, 0.5)); // 2.0 (平方根)// 指数运算:e的x次方
System.out.println(Math.exp(1)); // 2.718281828459045// e为底的对数
System.out.println(Math.log(Math.E)); // 1.0// 10为底的对数
System.out.println(Math.log10(100)); // 2.0
开方运算:
// 平方根
System.out.println(Math.sqrt(16)); // 4.0// 立方根
System.out.println(Math.cbrt(27)); // 3.0
三角函数:三角函数所有角度参数都使用弧度而不是度数。
// 角度转弧度
double angleInDegrees = 45;
double angleInRadians = Math.toRadians(angleInDegrees);// 三角函数
System.out.println(Math.sin(angleInRadians)); // 0.7071067811865475
System.out.println(Math.cos(angleInRadians)); // 0.7071067811865476
System.out.println(Math.tan(angleInRadians)); // 0.9999999999999999// 反三角函数
System.out.println(Math.asin(0.5)); // 0.5235987755982989 (弧度)
System.out.println(Math.acos(0.5)); // 1.0471975511965976 (弧度)
System.out.println(Math.atan(1)); // 0.7853981633974483 (弧度)// 弧度转角度
double resultInRadians = Math.asin(0.5);
double resultInDegrees = Math.toDegrees(resultInRadians);
System.out.println(resultInDegrees); // 30.0
指数和对数函数:
// exp(x) = e^x
System.out.println(Math.exp(1)); // 2.718281828459045
System.out.println(Math.exp(2)); // 7.38905609893065// log(x) = ln(x) (自然对数)
System.out.println(Math.log(2.718281828459045)); // 1.0// log10(x) (常用对数)
System.out.println(Math.log10(100)); // 2.0// log1p(x) = ln(1+x) (对于小数值更精确)
System.out.println(Math.log1p(0.001)); // 0.0009995003330835332
随机数生成:
// 生成0.0到1.0之间的随机数(不包括1.0)
double random = Math.random();
System.out.println("随机数: " + random);// 生成指定范围的随机整数
// 生成1到10之间的随机整数
int randomInt = (int)(Math.random() * 10) + 1;
System.out.println("1-10之间的随机数: " + randomInt);// 生成min到max之间的随机整数
int min = 5, max = 15;
int randomInRange = (int)(Math.random() * (max - min + 1)) + min;
符号相关方法:
System.out.println(Math.signum(5)); // 1.0 (正数)
System.out.println(Math.signum(-5)); // -1.0 (负数)
System.out.println(Math.signum(0)); // 0.0
近似相等相关方法:
// 判断两个double值是否近似相等
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(Math.abs(a - b) < 1e-10); // true
混合运算相关方法:
// hypot(x, y) = √(x² + y²) (避免中间计算溢出)
System.out.println(Math.hypot(3, 4)); // 5.0// IEEEremainder(x, y) - IEEE 754规定的余数
System.out.println(Math.IEEEremainder(10, 3)); // 1.0
5.3 Character 类
5.3.1 Character 类介绍
Character
类是 Java 中 char
基本数据类型的包装类,位于 java.lang
包中。它提供了许多有用的静态方法来操作字符,是处理字符数据的重要工具。
public final
class Character implements java.io.Serializable, Comparable<Character> {// 最小进制public static final int MIN_RADIX = 2;// 最大进制public static final int MAX_RADIX = 36;// 最小char值public static final char MIN_VALUE = '\u0000';// 最大char值public static final char MAX_VALUE = '\uFFFF';...
Character
类的特点:
final
类:不能被继承- 包装
char
类型:将基本类型char
包装为对象 - 提供丰富的字符操作方法
- 实现
Comparable
和Serializable
接口
Character
类创建:
// 方式1:使用构造函数(已过时,不推荐)
Character ch1 = new Character('A');// 方式2:使用valueOf()方法(推荐)
Character ch2 = Character.valueOf('B');// 方式3:自动装箱(最常用)
Character ch3 = 'C'; // 自动调用valueOf()// 拆箱
char primitiveChar = ch3; // 自动调用charValue()
Character
类常用常量:
System.out.println(Character.MIN_VALUE); // '\u0000' (最小char值)
System.out.println(Character.MAX_VALUE); // '\uffff' (最大char值)
System.out.println(Character.MIN_RADIX); // 2 (最小进制)
System.out.println(Character.MAX_RADIX); // 36 (最大进制)
5.3.2 Character 类判断方法
基本类型判断:
char ch = 'A';System.out.println(Character.isLetter(ch)); // true - 是否为字母
System.out.println(Character.isDigit(ch)); // false - 是否为数字
System.out.println(Character.isLetterOrDigit(ch)); // true - 是否为字母或数字
System.out.println(Character.isWhitespace(ch)); // false - 是否为空白字符
System.out.println(Character.isUpperCase(ch)); // true - 是否为大写字母
System.out.println(Character.isLowerCase(ch)); // false - 是否为小写字母
System.out.println(Character.isISOControl(ch)); // false - 是否为ISO控制字符
Unicode 相关判断:
char ch = '€'; // 欧元符号System.out.println(Character.isUnicodeIdentifierStart(ch)); // true
System.out.println(Character.isUnicodeIdentifierPart(ch)); // true
System.out.println(Character.isDefined(ch)); // true - 是否为定义的字符
System.out.println(Character.isValidCodePoint(ch)); // true - 是否为有效码点
5.3.3 Character 类字符转换方法
大小写转换:
char ch = 'a';System.out.println(Character.toUpperCase(ch)); // 'A'
System.out.println(Character.toLowerCase(ch)); // 'a'
System.out.println(Character.toTitleCase('hello')); // 'H' (首字母大写)
进制转换:
// 数字字符转数值
System.out.println(Character.digit('9', 10)); // 9
System.out.println(Character.digit('A', 16)); // 10 (十六进制)
System.out.println(Character.digit('Z', 36)); // 35 (三十六进制)// 数值转字符
System.out.println(Character.forDigit(9, 10)); // '9'
System.out.println(Character.forDigit(10, 16)); // 'a'
5.3.4 Character 类应用示例
验证字符串是否为纯字母:
public static boolean isAlphabetic(String str) {if (str == null || str.isEmpty()) {return false;}for (char c : str.toCharArray()) {if (!Character.isLetter(c)) {return false;}}return true;
}// 测试
System.out.println(isAlphabetic("Hello")); // true
System.out.println(isAlphabetic("Hello123")); // false
统计字符串中各类字符数量:
public static void countCharacters(String str) {int letters = 0, digits = 0, spaces = 0, others = 0;for (char c : str.toCharArray()) {if (Character.isLetter(c)) {letters++;} else if (Character.isDigit(c)) {digits++;} else if (Character.isWhitespace(c)) {spaces++;} else {others++;}}System.out.println("字母: " + letters);System.out.println("数字: " + digits);System.out.println("空格: " + spaces);System.out.println("其他: " + others);
}// 测试
countCharacters("Hello 123 World!");
// 输出:
// 字母: 10
// 数字: 3
// 空格: 2
// 其他: 1
字符串首字母大写:
public static String capitalize(String str) {if (str == null || str.isEmpty()) {return str;}char firstChar = str.charAt(0);if (Character.isLowerCase(firstChar)) {return Character.toUpperCase(first
5.4 String 类
5.4.1 String 类介绍
String
类是 Java 中最常用和最重要的类之一,用于表示和操作字符串。它位于 java.lang
包中,因此无需导入即可使用。
String 类源码:
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {...
}
String
类的特点:
- 不可变性(Immutable):
String
对象一旦创建,其内容就不能被修改 final
类:不能被继承- 实现接口:
Serializable
,Comparable<String>
,CharSequence
- 字符串常量池:JVM 维护的特殊内存区域,用于存储字符串字面量
String
类的创建方式:
// 创建方式1:字符串字面量,推荐使用的方式
String str1 = "Hello World";
String str2 = "Hello World"; // 会重用字符串常量池中的对象System.out.println(str1 == str2); // true(引用相同对象)// 创建方式2:使用 new 关键字
String str3 = new String("Hello World");
String str4 = new String("Hello World");System.out.println(str3 == str4); // false(不同对象)
System.out.println(str3.equals(str4)); // true(内容相同)// 其他创建方式
// 从字符数组创建
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str5 = new String(chars);// 从字节数组创建
byte[] bytes = {72, 101, 108, 108, 111};
String str6 = new String(bytes);// 使用 StringBuilder 或 StringBuffer
String str7 = new StringBuilder().append("Hello").append(" ").append("World").toString();
5.4.2 String 类常用方法
获取 String
类的信息:
String str = "Hello World";// 获取长度
System.out.println(str.length()); // 11// 获取指定位置的字符
System.out.println(str.charAt(0)); // 'H'// 获取字符数组
char[] charArray = str.toCharArray();// 获取字节数组
byte[] byteArray = str.getBytes();// 获取长度
System.out.println(str.length()); // 11// 获取指定位置的字符
System.out.println(str.charAt(0)); // 'H'// 获取字符数组
char[] charArray = str.toCharArray();// 获取字节数组
byte[] byteArray = str.getBytes();java
String str = "Hello World";// 获取长度
System.out.println(str.length()); // 11// 获取指定位置的字符
System.out.println(str.charAt(0)); // 'H'// 获取字符数组
char[] charArray = str.toCharArray();// 获取字节数组
byte[] byteArray = str.getBytes();String str = "Hello World";// 获取长度
System.out.println(str.length()); // 11// 获取指定位置的字符
System.out.println(str.charAt(0)); // 'H'// 获取字符数组
char[] charArray = str.toCharArray();// 获取字节数组
byte[] byteArray = str.getBytes();
String
类比较操作:
String str1 = "hello";
String str2 = "HELLO";// 相等比较(区分大小写)
System.out.println(str1.equals(str2)); // false// 相等比较(不区分大小写)
System.out.println(str1.equalsIgnoreCase(str2)); // true// 比较大小(字典序)
System.out.println(str1.compareTo(str2)); // 正数(str1 > str2)
System.out.println(str1.compareToIgnoreCase(str2)); // 0(相等)// 检查前缀和后缀
System.out.println(str1.startsWith("he")); // true
System.out.println(str1.endsWith("lo")); // true
String
类查找操作:
String str = "Hello World";// 查找字符或子串位置
System.out.println(str.indexOf('o')); // 4(第一次出现)
System.out.println(str.lastIndexOf('o')); // 7(最后一次出现)
System.out.println(str.indexOf("World")); // 6
System.out.println(str.indexOf("Java")); // -1(未找到)// 检查是否包含
System.out.println(str.contains("World")); // true
String
类的字符串操作:
String str = " Hello World ";// 去除首尾空白
System.out.println(str.trim()); // "Hello World"// 大小写转换
System.out.println(str.toLowerCase()); // " hello world "
System.out.println(str.toUpperCase()); // " HELLO WORLD "// 替换操作
System.out.println(str.replace('l', 'L')); // " HeLLo WorLd "
System.out.println(str.replaceAll(" ", "_")); // "__Hello_World__"
System.out.println(str.replaceFirst(" ", "_")); // "_ Hello World "// 截取子串
System.out.println(str.substring(6)); // "World "
System.out.println(str.substring(6, 11)); // "World"
String
类字符串的分割和连接:
// 分割字符串
String sentence = "apple,banana,orange";
String[] fruits = sentence.split(",");
// fruits = {"apple", "banana", "orange"}// 使用正则表达式分割
String text = "one1two2three";
String[] parts = text.split("\\d");
// parts = {"one", "two", "three"}// 连接字符串(Java 8+)
String joined = String.join("-", "apple", "banana", "orange");
// joined = "apple-banana-orange"// 使用StringJoiner(更灵活)
StringJoiner joiner = new StringJoiner(",", "[", "]");
joiner.add("apple").add("banana").add("orange");
// joiner.toString() = "[apple,banana,orange]"
5.4.3 String 类的不可变性
一旦一个 String
对象被创建,它的值就不能被修改。任何看似“修改”字符串的操作,实际上都会创建一个新的 String
对象。
String s = "Hello";
s = s + " World"; // 看似修改,其实是创建新对象
System.out.println(s); // 输出:Hello World
- 最初的
"Hello"
对象仍然存在,没有被修改。 s + " World"
创建了一个新对象"Hello World"
。- 变量
s
现在指向这个新对象。
Java 设计者将 String
设计为不可变,是出于以下几个关键考虑:
- 安全性:字符串常用于数据库连接 URL、文件路径、用户名/密码、网络地址。如果
String
可变,攻击者可能在你不知情的情况下修改这些关键信息,不可变性确保了敏感信息不会被篡改。 - 字符串常量池优化:Java 使用 字符串常量池(在堆中的
String Pool
)来缓存字符串字面量,避免重复创建相同内容的字符串。如果 String 可变,修改一个引用会影响所有共享该字符串的变量,导致严重 bug。 - 线程安全:不可变对象天生就是线程安全的,因为它的状态不能改变,多个线程可以同时访问同一个
String
对象而无需同步,不可变性避免了并发编程中的竞争条件。 - 哈希码缓存:
String
内部缓存了hashCode
,因为字符串内容不变,所以哈希码也永远不会变。 - 类加载机制依赖:Java 的类加载器使用字符串作为类名,如果字符串可变,可能导致类加载错误或安全漏洞。
5.4.3 String 类不可变的实现原理
(1)使用 final
修饰类
使用 final
修饰的类不能被继承,可以防止子类破坏不可变性。
public final class String {...
}
(2)final
字符数组
存储字符串内容的字符数组是 final
修饰的,引用不能改变,虽然数组内容理论上可变,但 Java 通过不提供修改数组内容的方法来保证安全。
private final char value[];
(3)私有构造器与深拷贝
String
的构造器会对传入的字符数组进行拷贝,而不是直接引用外部数组。防止外部修改数组影响字符串内容。
public String(char[] value) {this.value = Arrays.copyOf(value, value.length);
}
(4)所有修改操作都返回新对象
String
类不可变性的缺点:频繁修改字符串效率低,针对这种情况,Java 也提供了另外两种可变字符串类:
StringBuilder
:单线程频繁拼接,非线程安全,性能高StringBuffer
:多线程频繁拼接,线程安全(方法加synchronized
修饰)
5.4.4 字符串常量池
字符串常量池是 JVM 中一块特殊的存储区域,用于缓存字符串字面量(String literals
),目的是避免重复创建相同内容的字符串对象,从而节省内存、提高性能。
字符串常量池本质上是一个哈希表(HashTable),存储字符串内容到对象引用的映射。从 JDK 7 开始,字符串常量池从方法区(永久代,PermGen)移到了堆内存(Heap)中。
Java 中创建 String
对象主要有两种方式:
- 字面量方式创建,这种方式是直接赋值给变量
new
关键字方式创建,这种方式一定会创建新对象
字面量方式创建 String
对象时,JVM 会先检查常量池中是否已有该字符串,如果有,直接返回引用;如果没有,创建新对象并放入常量池。
如下示例:s1 == s2
为 true,指向常量池中的同一个对象
String s1 = "Hello";
String s2 = "Hello";
new
关键字方式创建 String
对象时,new 一定会在堆中创建新的 String
对象,但创建的这个字面量如果在常量池中没有的情况下,该字面量仍会进入常量池。
如下示例:
String s3 = new String("Hello");
String s4 = new String("Hello");
s3 == s4
为 false(两个不同的对象)s3.equals(s4)
为 true(内容相同)
String
类提供了一个 intern()
方法,用于手动将字符串放入常量池。intern()
方法的作用:
- 如果常量池中已有相同内容的字符串,返回其引用;
- 如果没有,将该字符串加入常量池,并返回引用。
intern()
方法语法:
public native String intern();
intern()
方法示例:
String s1 = new String("Hello"); // 在堆中创建新对象
String s2 = s1.intern(); // 将 "Hello" 入池,返回池中引用String s3 = "Hello"; // 字面量,从池中获取System.out.println(s1 == s2); // false(s1 是 new 出来的)
System.out.println(s2 == s3); // true(s2 和 s3 都指向常量池中的对象)
5.5 StringBuffer 类
5.5.1 StringBuffer 类介绍
StringBuffer
是 Java 中用于处理可变字符串的一个核心类,位于 java.lang
包中。
StringBuffer
解决了 String
类对象一旦创建内容就不可变的问题,允许在原有对象的基础上进行字符串的修改(如追加、插入、删除、替换等),而无需创建大量新的对象,从而在需要频繁修改字符串内容的场景下,比直接使用 String
更加高效。
StringBuffer
类部分源码:
public final class StringBufferextends AbstractStringBuilderimplements java.io.Serializable, CharSequence {private transient char[] toStringCache;public StringBuffer() {super(16);}// 构造方法:构造一个空的 StringBuffer,初始容量为 16 个字符public StringBuffer(int capacity) {super(capacity);}// 构造方法:构造一个空的 StringBuffer,指定初始容量public StringBuffer(String str) {super(str.length() + 16);append(str);}// 构造方法:构造一个包含指定字符串内容的 StringBuffer,初始容量为 str.length() + 16public StringBuffer(CharSequence seq) {this(seq.length() + 16);append(seq);}
StringBuffer
的所有公共方法都是同步的(synchronized
修饰),这意味着在多线程环境下,多个线程可以安全地共享和操作同一个 StringBuffer
实例,不会出现数据不一致的问题。但同时这种同步机制会带来一定的性能开销。
StringBuffer
的内部实现:
- 底层通常使用一个可动态扩容的字符数组 (
char[]
) 来存储字符序列。 - 维护一个
count
变量来记录当前实际包含的字符数量。 - 维护一个
capacity
(容量),表示内部字符数组的当前大小。当添加字符导致容量不足时,StringBuffer
会自动创建一个更大的数组并复制内容(扩容)。
5.5.2 StringBuffer 类常用方法
StringBuffer
构造方法示例:
// 构造一个初始容量为 16 个字符的StringBuffer
StringBuffer sb1 = new StringBuffer();// 构造一个空的 StringBuffer,指定初始容量
StringBuffer sb2 = new StringBuffer("abc");// 构造一个初始容量为指定字符长度的StringBuffer
StringBuffer sb3 = new StringBuffer(10);// 构造一个包含指定字符串内容的 StringBuffer,初始容量为 str.length() + 16
StringBuffer sb4 = new StringBuffer(sb2);
StringBuffer
写操作:
// append() - 追加内容 (最常用)
StringBuffer sb1 = new StringBuffer("这是StringBuffer初始内容");
sb1.append(" 追加字符串") // 可以追加字符串.append(123) // 可以追加数字.append(true) // 可以追加布尔值.append('!'); // 可以追加字符
System.out.println("sb1 = " + sb1); // sb1 = 这是StringBuffer初始内容 追加字符串123true!// insert() - 在指定位置插入内容
StringBuffer sb2 = new StringBuffer("这是StringBuffer初始内容");
sb2.insert(4, "【插入】"); // 在索引4处插入
System.out.println("sb2 = " + sb2); // sb2 = 这是St【插入】ringBuffer初始内容// delete() - 删除指定范围的内容
StringBuffer sb3 = new StringBuffer("这是StringBuffer初始内容");
sb3.delete(0, 4); // 删除索引0到3的字符 ("初始")
System.out.println("sb3 = " + sb3); // sb3 = ringBuffer初始内容// deleteCharAt() - 删除指定索引的单个字符
StringBuffer sb4 = new StringBuffer("这是StringBuffer初始内容");
sb4.deleteCharAt(3); // 删除索引3处的字符
System.out.println("sb4 = " + sb4); // sb4 = 这是SringBuffer初始内容// replace() - 替换指定范围的内容
StringBuffer sb5 = new StringBuffer("这是StringBuffer初始内容");
sb5.replace(3, 7, "替换"); // 将索引3到6的字符替换为 "替换"
System.out.println("sb5 = " + sb5); // sb5 = 这是S替换gBuffer初始内容// reverse() - 反转字符序列
StringBuffer sb6 = new StringBuffer("这是StringBuffer初始内容");
sb6.reverse();
System.out.println("sb6 = " + sb6); // sb6 = 容内始初reffuBgnirtS是这// setCharAt() - 修改指定索引的字符
StringBuffer sb7 = new StringBuffer("这是StringBuffer初始内容");
sb7.setCharAt(0, '首'); // 将第一个字符改为 '首'
System.out.println("sb7 = " + sb7); // sb7 = 首是StringBuffer初始内容
StringBuffer
查找操作:
// charAt() - 获取指定索引的字符
StringBuffer sb1 = new StringBuffer("这是StringBuffer初始内容");
char ch = sb1.charAt(2);
System.out.println("ch = " + ch); // ch = S// length() 和 capacity() - 获取长度和容量
StringBuffer sb2 = new StringBuffer("这是StringBuffer初始内容");
System.out.println("sb2长度 (字符数): " + sb2.length()); // sb2长度 (字符数): 18
System.out.println("sb2容量 (内部数组大小): " + sb2.capacity()); // sb2容量 (内部数组大小): 34// ensureCapacity() - 确保最小容量
StringBuffer sb3 = new StringBuffer("这是StringBuffer初始内容");
sb3.ensureCapacity(50); // 确保容量至少为50
System.out.println("ensureCapacity(50)后, 容量: " + sb3.capacity()); // ensureCapacity(50)后, 容量: 70// setLength() - 设置长度
StringBuffer sb4 = new StringBuffer("这是StringBuffer初始内容");
sb4.setLength(10); // 将长度设置为10, 超出部分被截断
System.out.println("setLength(10)后: \"" + sb4 + "\""); // setLength(10)后: "这是StringBu"
sb4.setLength(20); // 将长度设置为20, 不足部分用空字符填充
System.out.println("setLength(20)后: \"" + sb4 + "\" "); // setLength(20)后: "这是StringBu "// toString() - 转换为 String 对象
StringBuffer sb5 = new StringBuffer("这是StringBuffer初始内容");
String finalString = sb5.toString();
System.out.println("转换为String: \"" + finalString + "\""); // 转换为String: "这是StringBuffer初始内容"
System.out.println("类型: " + finalString.getClass().getSimpleName()); // 类型: String// 重要提示: toString() 返回的是新String对象, 原StringBuffer仍可修改
sb5.append(" - 原缓冲区继续修改");
System.out.println("原StringBuffer继续修改后: \"" + sb5 + "\""); // 原StringBuffer继续修改后: "这是StringBuffer初始内容 - 原缓冲区继续修改"
System.out.println("但String对象不变: \"" + finalString + "\""); // 但String对象不变: "这是StringBuffer初始内容"
5.6 StringBuilder 类
5.6.1 StringBuilder 类介绍
StringBuilder
是 Java 5 (JDK 1.5) 中引入的一个核心类,位于 java.lang
包中。
StringBuilder
与 StringBuffer
非常相似,都是用来处理可变字符串的,允许在原有对象的基础上进行高效的字符串修改(如追加、插入、删除、替换等),解决了 String
类不可变带来的性能问题。
StringBuilder
部分源码:
public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequence {// 构造一个空的 StringBuilder,初始容量为 16 个字符public StringBuilder() {super(16);}// 构造一个空的 StringBuilder,指定初始容量public StringBuilder(int capacity) {super(capacity);}// 构造一个包含指定字符串内容的 StringBuilder,初始容量为 str.length() + 16public StringBuilder(String str) {super(str.length() + 16);append(str);}// 用一个已有的字符序列(如字符串)来初始化一个新的 StringBuilder 对象public StringBuilder(CharSequence seq) {this(seq.length() + 16);append(seq);}
}
StringBuilder
与 StringBuffer
最关键的区别: StringBuilder
不是线程安全的,它的方法没有使用 synchronized
关键字修饰。
StringBuilder
内部实现:
- 与
StringBuffer
几乎完全相同。底层使用一个可动态扩容的字符数组 (char[]
) 存储字符。 - 维护
count
(当前字符数) 和capacity
(内部数组大小),当添加字符导致容量不足时,会自动进行扩容。
5.6.1 StringBuilder 类常用方法
StringBuilder
类写入/修改类方法:
// append(...) - 追加内容到末尾
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // -> "Hello World"
sb.append("!").append(2025).append('.');
System.out.println(sb); // 输出: Hello World!2025.// insert(int offset, ...) - 在指定位置插入内容
StringBuilder sb = new StringBuilder("HelloWorld");
sb.insert(5, " "); // 在索引5处插入空格 -> "Hello World"
sb.insert(0, "["); // 在开头插入 -> "[Hello World"
sb.insert(sb.length(), "]"); // 在末尾插入 -> "[Hello World]"
System.out.println(sb); // 输出: [Hello World]// replace(int start, int end, String str) - 替换指定范围的内容,start (包含) 到 end (不包含)
StringBuilder sb = new StringBuilder("I like Java programming.");
sb.replace(7, 11, "C++"); // 将 "Java" 替换为 "C++" -> "I like C++ programming."
System.out.println(sb); // 输出: I like C++ programming.// setCharAt(int index, char ch) - 修改指定索引的单个字符
StringBuilder sb = new StringBuilder("hello");
sb.setCharAt(0, 'H'); // 将第一个字符 'h' 改为 'H' -> "Hello"
System.out.println(sb); // 输出: Hello// reverse() - 反转字符序列
StringBuilder sb = new StringBuilder("abcde");
sb.reverse(); // 反转 -> "edcba"
System.out.println(sb); // 输出: edcba// delete(int start, int end) - 删除指定范围的内容,start (包含) 到 end (不包含)
StringBuilder sb = new StringBuilder("Remove this part please.");
sb.delete(7, 18); // 删除 "this part " -> "Remove please."
System.out.println(sb); // 输出: Remove please.// deleteCharAt(int index) - 删除指定索引的单个字符
StringBuilder sb = new StringBuilder("Hello");
sb.deleteCharAt(4); // 删除索引4的 'o' -> "Hell"
System.out.println(sb); // 输出: Hell// setLength(int newLength) - 设置长度
// 如果 newLength 大于当前长度,则用空字符 ('\u0000') 填充到新长度。
// 如果 newLength 小于当前长度,则从 newLength 处开始截断。
StringBuilder sb = new StringBuilder("Short");
sb.setLength(3); // 截断 -> "Sho"
System.out.println("截断后: '" + sb + "'"); // 输出: 截断后: 'Sho'sb.setLength(8); // 填充 -> "Sho\0\0\0\0\0" (后面5个空字符)
System.out.println("填充后长度: " + sb.length()); // 输出: 填充后长度: 8
// 注意: 空字符在控制台通常不可见
StringBuilder
类查找/查询类方法:
// charAt(int index) - 获取指定索引的字符
StringBuilder sb = new StringBuilder("Index");
char first = sb.charAt(0); // -> 'I'
char last = sb.charAt(sb.length() - 1); // -> 'x'
System.out.println("首字符: " + first + ", 末字符: " + last); // 输出: 首字符: I, 末字符: x// length() - 获取当前字符序列的长度
StringBuilder sb = new StringBuilder("Length Test");
System.out.println("长度: " + sb.length()); // 输出: 长度: 11// capacity() - 获取当前容量
StringBuilder sb = new StringBuilder();
System.out.println("初始容量: " + sb.capacity()); // 通常是 16
sb.append("A".repeat(20)); // 添加20个A
System.out.println("添加20个A后容量: " + sb.capacity()); // 可能变为 34 或更大 (16*2+2)
StringBuilder
类容量管理类方法:
ensureCapacity(int minimumCapacity)
:确保最小容量。确保此 StringBuilder
的容量至少为指定的最小值。如果当前容量小于 minimumCapacity
,则进行扩容。
StringBuilder sb = new StringBuilder();
System.out.println("扩容前容量: " + sb.capacity()); // 16
sb.ensureCapacity(100); // 确保容量至少为100
System.out.println("ensureCapacity(100)后容量: " + sb.capacity()); // >= 100
转换类方法:toString()
转换为 String
对象,返回一个包含此 StringBuilder
当前字符序列的新 String
对象。
这是唯一能将 StringBuilder 内容转换为不可变 String 的方法。之后对 StringBuilder 的修改不会影响已生成的 String。
StringBuilder sb = new StringBuilder("Final Result");
String resultStr = sb.toString(); // 创建一个新String
sb.append(" - Modified"); // 修改StringBuilder
System.out.println("StringBuilder: " + sb); // 输出: Final Result - Modified
System.out.println("String: " + resultStr); // 输出: Final Result (未改变)
5.7 Scanner 类
5.7.1 Scanner 类介绍
Scanner
是 Java 中用于解析基本类型和字符串的实用工具类,位于 java.util
包中。
Scanner
提供了一种简单、便捷的方式来读取用户从控制台(标准输入)、文件、字符串等不同来源输入的数据,并能根据分隔符(默认是空白字符,如空格、制表符、换行符)自动将输入流分割成“令牌 (tokens)”,然后将这些令牌解析成相应的数据类型(如 int, double, String 等)。
Scanner
可以从 InputStream
(如 System.in
)、File
、String
、Readable
等多种来源读取数据。能够将输入的字符串自动解析为 int
, long
, float
, double
, boolean
, String
等基本类型和字符串。
Scanner
部分源码:
public final class Scanner implements Iterator<String>, Closeable {// 从 InputStream 读取数据。最常用的是 new Scanner(System.in) 读取控制台输入public Scanner(InputStream source) {this(new InputStreamReader(source), WHITESPACE_PATTERN);}
}// 从指定的 File 对象读取数据public Scanner(File source) throws FileNotFoundException {this((ReadableByteChannel)(new FileInputStream(source).getChannel()));}// 从指定的字符串中读取数据public Scanner(String source) {this(new StringReader(source), WHITESPACE_PATTERN);}// 从指定的 Path (Java 7+) 读取数据public Scanner(Path source) throws IOException {this(Files.newInputStream(source));}
5.7.2 Scanner 类常用方法
(1)读取基本类型和字符串:
next()
:读取下一个由分隔符分隔的完整字符串(不包含分隔符)。遇到空白字符停止。
String word = scanner.next();
nextLine()
:读取当前行的剩余部分,包括空白字符,直到遇到行结束符(\n
或 \r\n
)。注意: 它会消耗行结束符。
String line = scanner.nextLine();
nextInt()
:读取下一个令牌并解析为 int。
int num = scanner.nextInt();
nextLong()
:读取下一个令牌并解析为 long。
long bigNum = scanner.nextLong();
```javanextDouble()
:读取下一个令牌并解析为 double。
double price = scanner.nextDouble();
`nextFloat()`:读取下一个令牌并解析为 float。```java
float rate = scanner.nextFloat();
nextBoolean()
:读取下一个令牌并解析为 boolean(必须是 “true” 或 “false”,不区分大小写)。
boolean flag = scanner.nextBoolean();
(2)检查输入状态:在调用 nextXxx()
方法前,强烈建议使用对应的 hasNextXxx()
方法检查下一个令牌是否可以成功解析为该类型,以避免 InputMismatchException
异常。
hasNext()
:检查是否有下一个令牌可用。
if (scanner.hasNext()) {...
}
hasNextLine()
:检查是否有下一行可用。
if (scanner.hasNextLine()) {...
}
hasNextInt()
:检查下一个令牌是否可以解析为 int。
if (scanner.hasNextInt()) {int num = scanner.nextInt();
}
hasNextDouble()
:检查下一个令牌是否可以解析为 double。
if (scanner.hasNextDouble()) {double d = scanner.nextDouble();
}
hasNextBoolean()
:检查下一个令牌是否可以解析为 boolean。
if (scanner.hasNextBoolean()) {boolean b = scanner.nextBoolean();
}
(3)其他常用方法
close()
:关闭 Scanner。强烈建议在使用完毕后调用此方法,特别是当它关联到文件或网络流时,以释放系统资源。
scanner.close();
useDelimiter(Pattern pattern)
/useDelimiter(String pattern)
:设置分隔符。参数可以是正则表达式字符串或 Pattern 对象。
scanner.useDelimiter(",\\s*"); // 用逗号和可选空格作为分隔符
delimiter()
:返回当前的分隔符模式。
Pattern delim = scanner.delimiter();
5.7.3 Scanner 类使用示例
读取控制台输入:
public static void main(String[] args) {// 创建 Scanner 读取控制台输入Scanner scanner = new Scanner(System.in);System.out.print("请输入您的姓名: ");String name = scanner.nextLine(); // 读取整行System.out.print("请输入您的年龄: ");// 使用 hasNextInt() 检查输入是否为整数while (!scanner.hasNextInt()) {System.out.println("输入无效!请输入一个整数:");scanner.next(); // 清除错误的输入}int age = scanner.nextInt();System.out.print("请输入您的身高 (米): ");// 使用 hasNextDouble() 检查输入是否为浮点数while (!scanner.hasNextDouble()) {System.out.println("输入无效!请输入一个数字:");scanner.next(); // 清除错误的输入}double height = scanner.nextDouble();System.out.println("\n--- 个人信息 ---");System.out.println("姓名: " + name);System.out.println("年龄: " + age + " 岁");System.out.println("身高: " + height + " 米");// 关闭 Scanner (良好习惯)scanner.close();
}
读取字符串中的数据 (解析 CSV 片段):
public static void main(String[] args) {// 模拟一个 CSV 格式的字符串String csvData = "苹果,15.5,库存充足\n香蕉,8.0,缺货\n橙子,12.8,库存充足";// 创建 Scanner 从字符串读取Scanner scanner = new Scanner(csvData);// 设置分隔符为逗号和可选空格scanner.useDelimiter(",\\s*|\\n");System.out.println("商品信息:");while (scanner.hasNext()) {String fruit = scanner.next(); // 商品名// 检查并读取价格if (scanner.hasNextDouble()) {double price = scanner.nextDouble();String status = scanner.next(); // 库存状态System.out.printf("%s: %.1f元, %s%n", fruit, price, status);} else {System.out.println("价格数据错误!");break;}}scanner.close();
}
读取文件 (需要处理异常):
public static void main(String[] args) {try {// 创建 Scanner 从文件读取File file = new File("data.txt"); // 假设文件存在Scanner scanner = new Scanner(file);System.out.println("文件内容:");while (scanner.hasNextLine()) {String line = scanner.nextLine();System.out.println(line);}scanner.close();} catch (FileNotFoundException e) {System.err.println("文件未找到: " + e.getMessage());}// 注意: 如果文件路径错误或不存在,会抛出 FileNotFoundException
}
5.7.4 Scanner 类使用注意
next()
vs nextLine()
:
next()
读取到下一个空白字符(空格、制表符、换行符)为止。nextLine()
读取从当前位置到行尾的所有字符(包括中间的空格),并消耗行结束符。- 当
nextInt()
/nextDouble()
等方法后紧跟nextLine()
时,nextLine()
会立即返回,因为它读取了nextInt()
留下的行结束符。解决方法是在nextLine()
前额外调用一次nextLine()
来"吃掉"残留的换行符,或者始终使用nextLine()
读取然后用Integer.parseInt()
等转换。
- 当
资源管理: 在使用完 Scanner
后一定要调用 close()
方法来关闭资源,尤其是在读取文件时,以防止资源泄漏。
异常处理: nextXxx()
方法在输入无法解析时会抛出 InputMismatchException
,可以使用 hasNextXxx()
进行预检查来避免异常。
性能: Scanner
适合简单的输入解析,但对于大量数据或高性能要求的场景就不太适合了,这个时候建议使用 BufferedReader
。