博主介绍: 大家好,我是想成为Super的Yuperman,互联网宇宙厂经验,17年医疗健康行业的码拉松奔跑者,曾担任技术专家、架构师、研发总监负责和主导多个应用架构。
近期专注: DeepSeek应用,RPA应用研究,主流厂商产品使用,开源RPA 应用等
技术范围: java体系,软件架构,DDD,多年Golang、.Net、Oracle等经验
业务范围: 对传统业务应用技术转型,从数字医院到区域医疗,从院内业务系统到互联网医院及健康服务,从公立医院到私立医院都有一些经历及理解
*** 为大家分享一些思考与积累,欢迎持续关注公众号:【火星求索】 ***
背景知识
Java 相关概念
- JavaSE (Java Standard Edition): 基础版,用于开发桌面应用程序。
- JavaEE (Java Enterprise Edition): 企业版,用于开发企业级应用程序。
- JavaME (Java Micro Edition): 微型版,用于开发嵌入式系统和移动设备应用程序。
编译与运行
-
编译阶段:
- 源文件:
.java
文件。 - 字节码文件:
.class
文件。 - 编译工具:
javac.exe
,用于将.java
文件编译为.class
文件。- 命令:
javac 文件名.java
- 编译包:
javac -d 编译后存放路径 java源文件路径
- 命令:
- 源文件:
-
运行阶段:
- 运行工具:
java.exe
,用于运行.class
文件。- 命令:
java 类名
(不带.class
后缀)
- 命令:
- JVM (Java Virtual Machine): Java 虚拟机,负责执行字节码文件。
- 运行工具:
开发环境
- JDK (Java Development Kit): Java 开发工具包,包含编译器、调试器等开发工具。
- JRE (Java Runtime Environment): Java 运行环境,包含 JVM 和运行 Java 程序所需的库。
- JVM (Java Virtual Machine): Java 虚拟机,负责执行字节码文件。
工具与格式
- native2ascii: 用于将 Unicode 字符转换为
\u
表示的 ASCII 格式。 - UML (Unified Modeling Language): 面向对象设计图,用于表示类、接口、继承、实现等关系。
- 空心箭头: 指向父类(继承)。
- 空心虚线箭头: 指向接口(实现)。
- 实心实线箭头: 表示关联关系。
注释
- 单行注释:
//
- 多行注释:
/* */
- 文档注释:
/** */
,用于生成帮助文档。
类与方法结构
类体 {方法体 {java语句;}
}AI写代码java运行
总结
- JavaSE 是基础版,JavaEE 是企业版,JavaME 是微型版。
- 编译 使用
javac
,运行 使用java
。 - JDK 是开发工具包,JRE 是运行环境,JVM 是虚拟机。
- UML 用于面向对象设计,注释 用于代码说明。
- 类与方法 的基本结构如上所示。
Java SE API 和文档
一、集成开发环境(IDEA)
以下是用户提供的快捷键和组织方式的总结:
组织方式
- Project(工程): 最高层级,包含多个模块。
- Module(模块): 工程下的子模块,包含多个包。
- Package(包): 模块下的子包,用于组织类和资源。
字体设置
- 路径:
File -> Settings -> Font
用于调整编辑器的字体样式和大小。
快捷键分类总结
导航与操作
-
展开/移动列表:
- 左右箭头: 展开或折叠列表。
- 上下箭头: 在列表中移动。
-
切换与定位:
- Alt+左右箭头: 切换 Java 程序。
- Alt+上下箭头: 在方法间快速移动。
- Alt+标号: 打开标号窗口。
- Ctrl+G: 定位到文件的某一行。
- Ctrl+点击: 切换源码。
- Ctrl+H: 查看实现类。
-
查找与搜索:
- Ctrl+Shift+N: 查找文件。
- Ctrl+N: 查找类文件。
- Ctrl+F12: 在当前类中查找一个方法。
编辑与格式化
-
代码编辑:
- Ctrl+Y: 删除一行。
- Shift+F6: 重命名。
- Alt+拖动: 一次编辑多行。
- Ctrl+Alt+T: 将选中的代码放在
TRY{}
、IF{}
、ELSE{}
中。
-
代码提示与自动补全:
- Ctrl+空格: 代码提示。
- Ctrl+P: 方法参数提示。
- Ctrl+J: 自动代码。
- Ctrl+Alt+Space: 类名或接口名提示。
-
格式化与优化:
- Ctrl+Alt+L: 格式化代码。
- Ctrl+Alt+I: 自动缩进。
- Ctrl+Alt+O: 优化导入的类和包。
运行与纠错
-
运行程序:
- Ctrl+Shift+F10: 运行当前程序。
-
纠错与提示:
- Alt+回车: 纠错提示。
窗口操作
- 全屏模式:
- Ctrl+Shift+F12: 切换全屏模式。
总结
- 组织方式: 工程 -> 模块 -> 包,层级清晰,便于管理。
- 快捷键:
- 导航与查找:快速定位文件、类、方法。
- 编辑与格式化:提高代码编写效率。
- 运行与纠错:快速运行程序并修复错误。
- 窗口操作:优化开发环境布局。
二、JVM内存划分
局部变量在方法体中声明,运行阶段内存在栈中分配
方法区内存:字节码文件在加载 的时候将其放在方法区之中(最先有数据,调用方法时在栈内分配空间)
堆内存(heap):new对象(成员变量中的实例变量(一个对象一份)在java对象内部存储),只能通过引用调用操作
栈(stack)内存:栈帧永远指向栈顶元素,栈顶元素处于活跃状态,先进后出,后进先出(存储局部变量)
内存区域与数据存储
-
堆内存(Heap):
- 存储实例变量(对象属性)。
- 每个 JVM 实例只有一个堆内存,所有线程共享。
- 垃圾回收器(GC)主要针对堆内存进行回收。
-
方法区(Method Area):
- 存储静态变量(类变量)和类元数据(如类信息、常量池等)。
- 每个 JVM 实例只有一个方法区,所有线程共享。
- 方法区是最先有数据的内存区域,因为类加载时静态变量和类信息会初始化。
-
栈内存(Stack):
- 存储局部变量和方法调用栈帧。
- 每个线程有一个独立的栈内存,线程私有。
- 栈内存是使用最频繁的内存区域,因为方法调用和局部变量的生命周期较短。
变量存储位置
-
局部变量:
- 存储在栈内存中。
- 生命周期与方法调用一致,方法结束时局部变量会被销毁。
-
实例变量:
- 存储在堆内存中。
- 生命周期与对象一致,对象被垃圾回收时实例变量会被销毁。
-
静态变量:
- 存储在方法区中。
- 生命周期与类一致,类卸载时静态变量会被销毁。
垃圾回收器(GC)
-
主要目标:
- 垃圾回收器主要针对堆内存进行回收,清理不再使用的对象。
- 栈内存和方法区的垃圾回收机制与堆内存不同。
-
特点:
- 堆内存是垃圾回收的主要区域,因为对象生命周期较长且占用内存较大。
- 栈内存和方法区的垃圾回收效率较高,因为它们的生命周期较短且数据量相对较小。
三、关键字:
类与关键字
-
public
:- 表示公开的类,类名必须与文件名一致,且一个文件中只能有一个
public
类。
- 表示公开的类,类名必须与文件名一致,且一个文件中只能有一个
-
class
:- 用于定义一个类。
-
static
:- 表示静态的,修饰的成员变量或方法属于类级别,不依赖于对象。
- 静态变量在类加载时初始化,存储在方法区内存中。
- 静态方法不能访问实例变量或实例方法,需要通过对象访问。
-
break
:- 用于跳出循环或
switch
语句。
- 用于跳出循环或
-
continue
:- 用于跳过当前循环的剩余部分,直接进入下一次循环。
- 语法:
continue 循环名称;
或循环名称:
。
-
this
:- 表示当前对象的引用。
- 用于区分局部变量和实例变量,或在构造方法中调用其他构造方法(
this(实参)
)。 - 不能用于静态方法中。
-
native
:- 用于调用 JVM 本地程序。
输入与输出
-
System.out.println()
:- 控制台输出,
println
表示输出并换行。
- 控制台输出,
-
键盘输入:
- 创建键盘扫描器对象:
java.util.Scanner s = new java.util.Scanner(System.in);
- 字符串输入:
String user = s.next();
- 整数输入:
int num = s.nextInt();
- 创建键盘扫描器对象:
final
关键字
-
修饰类:
- 类不能被继承。
-
修饰方法:
- 方法不能被重写。
-
修饰变量:
- 变量不能被修改。
- 修饰的成员变量必须手动赋值。
- 修饰的引用一旦指向一个对象,就不能指向其他对象,但所指向的内存可以修改。
-
常量:
- 定义常量:
public static final 类型 常量名 = 值;
- 命名规则:全部大写,用下划线分隔。
- 定义常量:
super
关键字
-
作用:
- 代表当前对象的父类型特征。
- 用于访问父类的属性、方法或调用父类的构造方法。
-
语法:
- 访问父类属性或方法:
super.
- 调用父类构造方法:
super()
- 访问父类属性或方法:
-
规则:
- 不能用于静态方法中。
- 如果父类和子类有同名属性,访问父类属性时不能省略
super
。 - 构造方法的第一行如果没有
this()
或super()
,默认会调用super()
。
static
关键字
-
静态变量:
- 属于类级别,不依赖于对象,类加载时初始化。
-
静态方法:
- 类级别的方法,不能访问实例变量或实例方法。
-
静态代码块:
- 在类加载时执行,只执行一次。
- 语法:
static {}
-
实例代码块:
- 在构造方法执行之前执行,用于对象初始化。
包与导入
-
package
:- 用于管理类,命名规则:公司域名倒序.项目名.模块名.功能名。
- 语法:
package 包名;
-
import
:- 用于导入包中的类。
- 语法:
import 包名.类名;
或import 包名.*;
java.lang.*
是核心语言包,无需导入。
-
快捷键:
Ctrl+Shift+O
:自动导入。
访问控制权限修饰符
-
private
:- 私有访问权限,只能在本类中访问。
-
default
:- 默认访问权限,可以被本包中的其他类访问。
-
protected
:- 受保护的访问权限,可以被本包及不同包的子类访问。
-
public
:- 公共访问权限,可以在任何地方访问。
-
类的修饰符:
- 类只能使用
public
或默认修饰符(缺省),内部类除外。
- 类只能使用
总结
- 类与关键字:
public
、class
、static
、this
、super
等关键字的作用与用法。 - 输入与输出:控制台输出与键盘输入的基本操作。
final
:用于修饰类、方法、变量,表示不可修改。static
:修饰类级别的成员,与对象无关。- 包与导入:
package
和import
的使用及命名规则。 - 访问控制权限:
private
、default
、protected
、public
的访问范围。
四、Java基础
以下是用户提供的内容的总结:
标识符
-
定义:
- 用户有权命名的单词,包括类名、方法名、常量名、变量名、接口名等。
-
命名规则:
- 类名、接口名: 首字母大写,后面每个单词首字母大写(大驼峰命名法)。
- 方法名、变量名: 首字母小写,后面每个单词首字母大写(小驼峰命名法)。
- 常量名: 全部大写,单词间用下划线分隔。
字面值
- 定义: 数据本身,如数字、字符串等,通常以紫色显示。
变量
-
局部变量:
- 定义在方法体内,没有默认值,必须手动初始化。
- 生命周期与方法调用一致。
-
成员变量:
- 定义在类体内,有默认值(数值类型为 0,布尔类型为
false
,引用类型为null
)。 - 分为实例变量和静态变量。
- 定义在类体内,有默认值(数值类型为 0,布尔类型为
-
实例变量:
- 不带
static
关键字,属于对象级别。 - 必须通过对象引用访问(
引用.变量名
)。 - 存储在堆内存中。
- 不带
-
静态变量:
- 带
static
关键字,属于类级别。 - 在类加载时初始化,存储在方法区内存中。
- 通过类名访问(
类名.变量名
)。
- 带
引用
- 定义: 是一个变量,可以是实例变量或局部变量。
- 实例变量:
类名 引用 = new 类名();
- 局部变量:
引用 变量名 = new 引用();
- 实例变量:
数据类型
-
基本数据类型:
- 整数型:
byte
(1 字节)、short
(2 字节)、int
(4 字节)、long
(8 字节,后缀L
)。 - 浮点型:
float
(4 字节)、double
(8 字节)。 - 布尔型:
boolean
(1 字节)。 - 字符型:
char
(2 字节)。
- 整数型:
-
引用数据类型:
- 字符串:
String
,不可变,存储在方法区字符串池中。
- 字符串:
-
比较:
- 基本数据类型使用
==
判断相等。 - 引用数据类型(包括
String
)使用equals
判断相等。
- 基本数据类型使用
字符编码
- 发展顺序: ASCII < ISO-8859-1 < GB2312 < GBK < GB18030 < Big5 < Unicode(统一全球编码)。
位运算符
- 逻辑异或(^): 两边不一样为真。
- 短路与(&&): 左边为假时直接返回假。
- 按位与(&): 将操作数转换为二进制后按位与。
- 短路或(||): 左边为真时直接返回真。
- 左移(<<): 二进制数据左移,相当于乘以 2 的 N 次方。
- 右移:
- 带符号右移(>>): 正数用 0 填充,负数用 1 填充。
- 无符号右移(>>>): 无论正负都用 0 填充。
- 按位取反(~): 将二进制每一位取反,结果为
-(n+1)
。
方法(函数)
-
定义:
[修饰符列表] 返回值类型 方法名(形参列表) {方法体;return; // return 后不能跟语句 }AI写代码java运行
-
调用:
类名.方法名(实参列表);
-
实例方法: 不带
static
,需要对象参与。 -
静态方法: 带
static
,与对象无关。
方法重载(Overload)
- 定义: 在同一类中,方法名相同但参数列表不同。
- 特点: 与返回值类型和修饰符列表无关。
方法递归
- 定义: 方法调用自身,每次递归都会分配新的内存空间(压栈)。
-
示例:
public static int sum(int n) {if (n == 1) {return 1;}return n + sum(n - 1); }AI写代码java运行
方法覆盖(Override)
- 定义: 发生在继承关系中,子类重写父类的方法。
- 规则:
- 方法名、返回值类型、形参列表必须与父类一致。
- 访问权限不能比父类更低,抛出异常不能更多。
- 限制:
- 私有方法、构造方法不能覆盖。
- 静态方法不存在覆盖。
总结
- 标识符: 命名规则与用途。
- 变量: 局部变量、实例变量、静态变量的定义与存储位置。
- 数据类型: 基本数据类型与引用数据类型的区别。
- 位运算符: 各种位运算符的作用与用法。
- 方法: 定义、调用、重载、递归与覆盖的规则与特点。
五、Java 控制流与 Lambda 表达式
1. 控制流语句
-
If-Else 语句:
if (条件) {// 语句 } else if (表达式) {// 语句 } else {// 语句 }AI写代码java运行
-
Switch 语句:
switch (关键词) {case 关键词:// java语句break;default:// 默认语句 }AI写代码java运行
-
For 循环:
for (初始表达式; 布尔表达式; 更新循环体) {// 循环体 }AI写代码java运行
-
增强 For 循环(For Each):
for (元素类型 变量名 : 数组或集合) {System.out.println(变量名); }AI写代码java运行
-
While 循环:
while (表达式) {// 循环体 }AI写代码java运行
-
Do-While 循环:
do {// 循环体 } while (布尔表达式);AI写代码java运行
2. Java 标签
- 标签用于控制嵌套循环的跳转和中断。
- 语法:
label:
- 用法:
continue label;
:跳过当前循环,继续执行标签处的循环。break label;
:结束标签处的循环,执行循环后的代码。
3. Lambda 表达式
-
实现 Runnable:
// Java 8 之前 new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Before Java8, too much code for too little to do");} }).start();// Java 8 方式 new Thread(() -> System.out.println("In Java8, Lambda expression rocks !!")).start();AI写代码java运行
-
事件处理:
// Java 8 之前 JButton show = new JButton("Show"); show.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("Event handling without lambda expression is boring");} });// Java 8 方式 show.addActionListener((e) -> {System.out.println("Light, Camera, Action !! Lambda expressions Rocks"); });AI写代码java运行
-
列表迭代:
// Java 8 之前 List<String> features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API"); for (String feature : features) {System.out.println(feature); }// Java 8 之后 features.forEach(n -> System.out.println(n)); // 使用方法引用 features.forEach(System.out::println);AI写代码java运行
-
Map 和 Reduce:
// 不使用 lambda 表达式 List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); for (Integer cost : costBeforeTax) {double price = cost + .12 * cost;System.out.println(price); }// 使用 lambda 表达式 costBeforeTax.stream().map((cost) -> cost + .12 * cost).forEach(System.out::println);// 使用 reduce 计算总和 double bill = costBeforeTax.stream().map((cost) -> cost + .12 * cost).reduce((sum, cost) -> sum + cost).get(); System.out.println("Total : " + bill);AI写代码java运行
-
对列表的每个元素应用函数:
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.", "Canada"); String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", ")); System.out.println(G7Countries);AI写代码java运行
-
计算集合元素的最大值、最小值、总和以及平均值:
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29); IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("Highest prime number in List : " + stats.getMax()); System.out.println("Lowest prime number in List : " + stats.getMin()); System.out.println("Sum of all prime numbers : " + stats.getSum()); System.out.println("Average of all prime numbers : " + stats.getAverage());AI写代码java运行
总结
- 控制流语句:用于控制程序的执行流程,包括条件判断、循环等。
- Java 标签:用于控制嵌套循环的跳转和中断。
- Lambda 表达式:简化了匿名类的使用,使代码更简洁,特别是在实现函数式接口(如
Runnable
、ActionListener
)时非常有用。 - Stream API:提供了强大的集合操作功能,如
map
、reduce
、forEach
等,使得对集合的处理更加高效和简洁。
六、面向对象
面向过程与面向对象的对比
-
面向过程:
- 因果关系:关注问题的具体步骤和流程。
- 具体过程:强调如何一步步解决问题。
- 耦合度高:各个模块之间依赖性强,修改一个模块可能会影响其他模块。
- 软件拓展性差:由于耦合度高,系统的扩展和维护较为困难。
-
面向对象:
- 分类对象:将问题分解为多个对象,每个对象负责特定的功能。
- 关系层度低:对象之间的依赖关系较弱,耦合度低。
- 关注对象功能:关注对象能完成哪些功能,而不是具体的实现步骤。
- 三大特征:
- 封装性:将复杂的事务封装起来,只保留简单的操作入口。封装后形成独立的对象,提高了代码的复用性、适应性和安全性。
- 继承性:实现代码复用,最重要的是支持多态和方法覆盖。
- 多态性:父类型的引用可以指向子类型对象,降低程序耦合度,提高扩展力。
面向对象的分析与设计
- 面向对象的分析(OOA):分析问题域,识别对象及其关系。
- 面向对象的设计(OOD):设计对象的结构和行为,定义类及其关系。
- 面向对象的编程(OOP):使用编程语言实现设计,创建对象并实现其功能。
类与对象
- 类:高度抽象的对象的集合,是一个模板。
- 静态代码块:类加载时执行。
- 实例代码块:实例化时执行。
- 静态变量:类级别的变量。
- 实例变量:对象级别的变量,存储在堆内存中。
- 构造方法:创建对象时调用,用于初始化实例变量。
- 静态方法:类级别的方法。
- 实例方法:对象级别的方法。
- 成员变量:对象的属性,描述对象的状态。
- 成员方法:对象的行为,描述对象的动作。
- 对象:类的具体实例。
- 创建对象:
类名 对象名称 = new 类名();
- 使用对象:
对象名称.属性名
或对象名称.方法名()
- 修改对象:
引用.变量名 = 值
- 引用与对象:引用保存了对象的地址,指向堆内存中的对象。多个引用可以指向同一个对象,但一个引用只能指向一个对象。
- 创建对象:
User u=new User();
Address a=new Address();
u.addr=a;
Print(u.addr.city);
A.city=”天津”;
Print(u.addr.city); AI写代码java运行
封装
- 私有化属性:使用
private
关键字将属性私有化。 - 提供操作入口:通过
getter
和setter
方法提供对属性的访问和修改。- 读取属性:
public 数据类型 get属性名() { return 属性; }
- 修改属性:
public void set属性名(数据类型 属性) { this.属性 = 属性; }
- 读取属性:
- 业务逻辑控制:在
setter
方法中添加业务逻辑进行安全控制。
构造方法
- 作用:创建对象并初始化实例变量。
- 语法:
修饰符 构造方法名(形参) { 构造方法体; this.实例变量 = 形参; }
- 特点:没有返回值类型,方法名与类名一致,不能使用
return
返回值,但可以使用return
结束方法。 - 调用:
new 构造方法名(实参)
- 缺省构造器:如果没有定义构造方法,编译器会自动生成一个无参的缺省构造器。
继承
- 语法:
[修饰符列表] class 子类名 extends 父类名 { 类体 = 属性 + 方法 }
- 单继承:Java中类只能继承一个父类。
- 继承关系:
- 父类:也称为基类、超类、
superclass
。 - 子类:也称为派生类、
subclass
。
- 父类:也称为基类、超类、
- 不可继承:私有的属性和方法、构造方法。
- 间接继承:通过继承链,子类可以间接继承父类的父类。
- 默认继承:如果没有显式继承任何类,默认继承
java.lang.Object
类。 - super关键字:用于调用父类的属性、方法和构造方法。
多态
- 向上转型(Upcasting):子类转换为父类型,自动类型转换。
- 语法:
父类 引用 = new 子类();
- 特点:编译通过,运行没有问题。
- 语法:
- 向下转型(Downcasting):父类转换为子类,强制类型转换。
- 语法:
子类 引用 = (子类) 父类引用;
- 特点:存在隐患,可能导致
ClassCastException
异常。
- 语法:
- 动态绑定:父类型引用指向子类型对象,调用方法时实际执行的是子类的方法。
- instanceof运算符:用于在强制转换前检查对象的类型,避免
ClassCastException
异常。- 语法:
引用 instanceof 数据类型名
- 返回值:布尔类型,
true
表示引用指向的对象是后面的数据类型,false
表示不是。
- 语法:
以下是关于 抽象类 和 接口 的总结:
抽象类
-
定义:
- 使用
abstract
关键字修饰的类,是类的进一步抽象。 - 属于引用数据类型。
- 使用
-
语法:
[修饰符列表] abstract class 类名 {}AI写代码java运行
-
特点:
- 不能使用
private
或final
修饰。 - 抽象类可以包含抽象方法和非抽象方法。
- 抽象类的子类可以是抽象类或非抽象类。
- 不能实例化(不能创建对象),但可以有构造方法,供子类使用。
- 不能使用
-
抽象方法:
- 使用
abstract
关键字修饰,无方法体。 - 语法:
[修饰符列表] abstract 返回值类型 方法名();
- 包含抽象方法的类一定是抽象类。
- 使用
-
规则:
- 抽象类不一定有抽象方法,但抽象方法必须出现在抽象类中。
- 非抽象类继承抽象类时,必须实现所有抽象方法。
接口
-
定义:
- 使用
interface
关键字定义,是完全抽象的(特殊的抽象类)。 - 属于引用数据类型。
- 使用
-
语法:
[修饰符列表] interface 接口名 {}AI写代码java运行
-
特点:
- 接口中只能包含常量和抽象方法(默认
public static final
和public abstract
,修饰符可省略)。 - 支持多继承,一个接口可以继承多个接口。
- 接口不能继承抽象类。
- 接口中只能包含常量和抽象方法(默认
-
方法类型:
- 抽象方法:
abstract
修饰(可省略)。 - 默认方法:
default
修饰,提供默认实现。 - 静态方法:
static
修饰,通过接口名调用。
- 抽象方法:
-
实现:
- 类通过
implements
关键字实现接口。 - 非抽象类实现接口时,必须重写所有抽象方法。
- 一个类可以实现多个接口。
- 类通过
-
多态:
- 接口支持多态:
父类型引用指向子类对象
。 - 示例:
接口名 引用 = new 实现类();
- 接口支持多态:
-
作用:
- 解耦合:调用者面向接口调用,实现者面向接口编写实现。
- 扩展性强:接口+多态可以降低程序耦合度。
抽象类与接口的区别
特性 | 抽象类 | 接口 |
---|---|---|
抽象程度 | 半抽象(可以包含具体方法) | 完全抽象(只能包含抽象方法) |
构造方法 | 有构造方法,供子类使用 | 无构造方法 |
继承 | 单继承(一个类只能继承一个抽象类) | 支持多继承(一个类可以实现多个接口) |
内容 | 可以包含抽象方法和非抽象方法 | 只能包含常量和抽象方法 |
用途 | 抽象行为和数据 | 主要抽象行为 |
实例化 | 不能实例化 | 不能实例化 |
开发中的选择
-
抽象类:
- 当多个类有共同的属性和行为,且需要部分具体实现时使用。
- 适合定义“是什么”(
is-a
关系)。
-
接口:
- 当需要定义一组行为规范,且不关心具体实现时使用。
- 适合定义“能做什么”(
like-a
关系)。
示例
-
抽象类:
abstract class Animal {abstract void sound();void sleep() {System.out.println("Sleeping...");} }AI写代码java运行
-
接口:
interface Flyable {void fly(); }AI写代码java运行
-
实现与继承:
class Bird extends Animal implements Flyable {@Overridevoid sound() {System.out.println("Chirp...");}@Overridepublic void fly() {System.out.println("Flying...");} }AI写代码java运行
总结
-
面向对象编程通过封装、继承和多态三大特征,提高了代码的复用性、扩展性和维护性。
-
类与对象是面向对象编程的基础,类是对对象的抽象,对象是类的实例。
-
封装通过私有化属性和提供操作入口,增强了代码的安全性和可控性。
-
继承实现了代码的复用,并支持多态和方法覆盖。
-
多态通过向上转型和向下转型,降低了程序的耦合度,提高了扩展力。
-
面向抽象编程,而不是面向具体,可以进一步降低耦合度,提高系统的灵活性和可扩展性。
-
抽象类 用于定义类的共有特征,支持部分具体实现。
-
接口 用于定义行为规范,支持多继承和解耦合。
-
在实际开发中,根据需求选择抽象类或接口,合理使用可以提高代码的扩展性和可维护性。
七、类库
源码、字节码与帮助文档
-
源码:
- 理解程序:源码是程序员编写的原始代码,用于理解程序的逻辑和功能。
-
字节码:
- 程序开发使用:字节码是源码编译后的中间代码,由JVM执行。它是跨平台的,可以在任何支持JVM的系统上运行。
-
帮助文档:
- 对开发提供帮助:帮助文档是开发者的参考指南,通常通过
javadoc
生成。 - 注意使用版本同一:确保使用的帮助文档与代码版本一致,避免因版本差异导致的错误。
- 对开发提供帮助:帮助文档是开发者的参考指南,通常通过
Object类(根类)
Object
是Java中所有类的根类,提供了一些核心方法:
-
protected Object clone()
:- 负责对象克隆,返回对象的副本。
-
boolean equals(Object obj)
:- 判断两个对象是否相等。默认比较引用地址,通常需要重写以比较对象内容。
-
int hashCode()
:- 返回对象的哈希代码值,用于哈希表等数据结构。
-
String toString()
:- 返回对象的字符串表示形式。默认返回类名@哈希值,通常需要重写以提供更有意义的信息。
-
protected void finalize() throws Throwable
:- 垃圾回收器负责调用,用于对象销毁前的清理工作。
-
System.gc()
:- 建议启动垃圾回收器,但不保证立即执行。
System类
System
类提供了一些系统级别的操作:
-
System.gc()
:- 建议启动垃圾回收器。
-
System.out
:- 静态变量,用于控制台输出。
-
System.out.print()
:- 输出打印不换行。
-
System.out.println()
:- 换行输出。
-
System.currentTimeMillis()
:- 获取自1970年1月1日00:00:00到当前系统时间的总毫秒数。
-
System.exit(0)
:- 退出JVM。
Arrays类
Arrays
是数组工具类,提供了一些常用方法:
-
Arrays.sort(arr)
:- 对数组进行排序。
-
Arrays.binarySearch(arr, key)
:- 使用二分法查找元素,不存在时返回-1。
String类
String
类用于操作字符串,提供了丰富的构造方法和方法:
-
构造方法:
String(byte[] byte)
:将字节数组转换为字符串。String(char[] char)
:将字符数组转换为字符串。String(String string)
:复制字符串。
-
常用方法:
char charAt(int index)
:返回指定索引的字符。int compareTo(String string)
:字典比较大小。boolean contains(String string)
:判断是否包含指定字符串。boolean endsWith(String string)
:判断是否以指定字符串结尾。boolean startsWith(String prefix)
:判断是否以指定前缀开头。boolean equals(Object anObject)
:比较字符串内容。boolean equalsIgnoreCase(String anotherString)
:忽略大小写比较。byte[] getBytes()
:将字符串转换为字节数组。int indexOf(String str)
:返回子字符串第一次出现的索引。int lastIndexOf(String str)
:返回子字符串最后一次出现的索引。boolean isEmpty()
:判断字符串是否为空。String replace(CharSequence target, CharSequence replacement)
:替换字符串。String substring(int beginIndex)
:截取字符串。char[] toCharArray()
:将字符串转换为字符数组。String toLowerCase()
:将字符串转换为小写。String toUpperCase()
:将字符串转换为大写。String[] split(String regex)
:按正则表达式拆分字符串。String trim()
:去除前后空白。static String valueOf()
:将其他类型转换为字符串。
StringBuffer与StringBuilder
-
StringBuffer:
- 线程安全,适用于多线程环境。
- 常用方法:
append()
、reverse()
。
-
StringBuilder:
- 非线程安全,性能优于
StringBuffer
。
- 非线程安全,性能优于
包装类
包装类用于将基本数据类型转换为对象:
- 常用包装类:
Integer
、Character
等。
- 常用方法:
int intValue()
:拆箱,将包装类转换为基本类型。static int parseInt(String s)
:将字符串转换为整数。
日期相关类
-
java.util.Date
:- 表示日期和时间。
-
SimpleDateFormat
:- 用于格式化日期。
- 常用方法:
format()
、parse()
。
数字相关类
-
DecimalFormat
:- 用于格式化数字。
-
BigDecimal
:- 用于高精度计算,适用于财务数据。
-
Random
:- 用于生成随机数。
枚举(Enum)
枚举是一种特殊的类,用于定义一组常量:
enum Season {SPRING, SUMMER, AUTUMN, WINTER
}AI写代码java运行
内部类
-
成员内部类:
- 定义在类中,可以访问外部类的所有成员。
-
局部内部类:
- 定义在方法中,只能在该方法内访问。
-
静态内部类:
- 使用
static
修饰,只能访问外部类的静态成员。
- 使用
-
匿名内部类:
- 没有名称的内部类,通常用于实现接口或抽象类。
总结
- 源码是理解程序的基础,字节码是程序运行的关键,帮助文档是开发的指南。
- Object是Java的根类,提供了对象的基本操作。
- System类提供了系统级别的操作,如垃圾回收、时间获取等。
- String类用于操作字符串,提供了丰富的构造方法和方法。
- StringBuffer和StringBuilder用于字符串的拼接和修改,前者线程安全,后者性能更优。
- 包装类用于将基本数据类型转换为对象。
- 日期相关类用于处理日期和时间。
- 内部类提供了更灵活的代码组织方式。
八、数组
一维数组
-
定义:
- 数组是引用数据类型,存储在堆内存中。
- 可以存储各种数据类型,但不能直接存储对象,存储的是对象的引用(内存地址)。
-
特点:
- 数组元素类型统一,最后一个下标为
length - 1
。 - 带有
length
属性,用于获取数组长度。
- 数组元素类型统一,最后一个下标为
-
优点:
- 查询、查找、检索某个下标元素效率极高(内存连续,类型相同)。
-
缺点:
- 随机增删元素效率较低。
- 不能存储大数据量。
-
定义与初始化:
-
静态初始化:
数据类型[] 数组名 = {元素1, 元素2, ...};AI写代码java运行
-
动态初始化:
数据类型[] 数组名 = new 数据类型[长度];AI写代码java运行
-
-
赋值:
数组名[下标] = 值;AI写代码java运行
-
遍历:
-
使用
for
循环或增强for
循环:for (int i = 0; i < 数组名.length; i++) {System.out.println(数组名[i]); }AI写代码java运行
-
-
方法参数:
-
数组可以作为方法的参数:
void 方法名(数据类型[] 数组名) {}AI写代码java运行
-
-
main 方法的数组参数:
-
main
方法的参数是一个字符串数组,用于接收命令行参数:public static void main(String[] args) {}AI写代码java运行
-
-
存储对象:
-
数组可以存储对象的引用:
类名[] 数组名 = new 类名[长度]; 数组名[0] = new 类名();AI写代码java运行
-
-
数组扩容:
-
新建一个大数组,然后将原数组拷贝过去:
int[] newArray = new int[原数组.length * 2]; System.arraycopy(原数组, 0, newArray, 0, 原数组.length);AI写代码java运行
-
-
数组拷贝:
-
使用
System.arraycopy
方法:System.arraycopy(原数组, 原起点, 目标数组, 目标下标, 长度);AI写代码java运行
-
二维数组
-
定义:
- 二维数组是数组的数组,可以看作是一个表格。
-
语法:
数据类型[][] 数组名 = new 数据类型[行数][列数];AI写代码java运行
-
初始化:
-
静态初始化:
数据类型[][] 数组名 = {{元素1, 元素2}, {元素3, 元素4}};AI写代码java运行
-
动态初始化:
数据类型[][] 数组名 = new 数据类型[行数][列数];AI写代码java运行
-
-
遍历:
-
使用嵌套
for
循环:for (int i = 0; i < 数组名.length; i++) {for (int j = 0; j < 数组名[i].length; j++) {System.out.println(数组名[i][j]);} }AI写代码java运行
-
总结
-
一维数组:
- 适用于存储一组相同类型的数据。
- 查询效率高,增删效率低。
- 可以通过
length
属性获取长度。 - 支持静态初始化和动态初始化。
-
二维数组:
- 适用于存储表格型数据。
- 可以看作是一维数组的数组。
- 支持静态初始化和动态初始化。
-
数组的优缺点:
- 优点:查询效率高,内存连续。
- 缺点:增删效率低,不能存储大数据量。
-
数组的应用场景:
- 存储一组固定长度的数据。
- 存储对象引用。
- 存储表格型数据(二维数组)。
示例
-
一维数组:
int[] arr = {1, 2, 3, 4, 5}; for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]); }AI写代码java运行
-
二维数组:
int[][] arr = {{1, 2}, {3, 4}}; for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {System.out.println(arr[i][j]);} }AI写代码java运行
-
数组存储对象:
Animal[] animals = new Animal[2]; animals[0] = new Cat(); animals[1] = new Dog();AI写代码java运行
-
数组扩容:
int[] src = {1, 2, 3}; int[] dest = new int[src.length * 2]; System.arraycopy(src, 0, dest, 0, src.length);AI写代码java运行
通过合理使用数组,可以高效地存储和操作数据,但需要注意其增删效率较低的缺点。
九、算法
以下是常见 排序算法 和 查找算法 的思想总结,并附带 Java 实例:
排序算法
-
冒泡排序(Bubble Sort):
-
思想:重复遍历数组,每次比较相邻元素,如果顺序错误则交换,直到没有需要交换的元素。
-
时间复杂度:O(n²)。
-
Java 实现:
public static void bubbleSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}} }AI写代码java运行
-
-
选择排序(Selection Sort):
-
思想:每次从未排序部分选择最小元素,放到已排序部分的末尾。
-
时间复杂度:O(n²)。
-
Java 实现:
public static void selectionSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {int minIndex = i;for (int j = i + 1; j < arr.length; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}int temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;} }AI写代码java运行
-
-
插入排序(Insertion Sort):
-
思想:将未排序部分的元素逐个插入到已排序部分的正确位置。
-
时间复杂度:O(n²)。
-
Java 实现:
public static void insertionSort(int[] arr) {for (int i = 1; i < arr.length; i++) {int key = arr[i];int j = i - 1;while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;} }AI写代码java运行
-
-
快速排序(Quick Sort):
-
思想:选择一个基准元素,将数组分为两部分,左边小于基准,右边大于基准,递归排序。
-
时间复杂度:O(n log n)。
-
Java 实现:
public static void quickSort(int[] arr, int low, int high) {if (low < high) {int pivot = partition(arr, low, high);quickSort(arr, low, pivot - 1);quickSort(arr, pivot + 1, high);} }private static int partition(int[] arr, int low, int high) {int pivot = arr[high];int i = low - 1;for (int j = low; j < high; j++) {if (arr[j] < pivot) {i++;int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}int temp = arr[i + 1];arr[i + 1] = arr[high];arr[high] = temp;return i + 1; }AI写代码java运行
-
-
归并排序(Merge Sort):
-
思想:将数组分成两半,分别排序,然后合并。
-
时间复杂度:O(n log n)。
-
Java 实现:
public static void mergeSort(int[] arr, int left, int right) {if (left < right) {int mid = (left + right) / 2;mergeSort(arr, left, mid);mergeSort(arr, mid + 1, right);merge(arr, left, mid, right);} }private static void merge(int[] arr, int left, int mid, int right) {int[] temp = new int[right - left + 1];int i = left, j = mid + 1, k = 0;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}while (i <= mid) {temp[k++] = arr[i++];}while (j <= right) {temp[k++] = arr[j++];}for (int p = 0; p < temp.length; p++) {arr[left + p] = temp[p];} }AI写代码java运行
-
查找算法
-
线性查找(Linear Search):
-
思想:从头到尾遍历数组,逐个比较,找到目标元素。
-
时间复杂度:O(n)。
-
Java 实现:
public static int linearSearch(int[] arr, int target) {for (int i = 0; i < arr.length; i++) {if (arr[i] == target) {return i;}}return -1; }AI写代码java运行
-
-
二分查找(Binary Search):
-
思想:在有序数组中,每次取中间元素与目标比较,缩小查找范围。
-
时间复杂度:O(log n)。
-
Java 实现:
public static int binarySearch(int[] arr, int target) {int left = 0, right = arr.length - 1;while (left <= right) {int mid = (left + right) / 2;if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return -1; }AI写代码java运行
-
总结
-
排序算法:
- 冒泡排序:简单但效率低,适合小规模数据。
- 选择排序:每次选择最小元素,适合小规模数据。
- 插入排序:适合部分有序的数据。
- 快速排序:高效,适合大规模数据。
- 归并排序:稳定且高效,适合大规模数据。
-
查找算法:
- 线性查找:适合无序数据。
- 二分查找:适合有序数据,效率高。
-
选择依据:
- 数据规模、是否有序、稳定性要求等。
示例
public class Main {public static void main(String[] args) {int[] arr = {5, 3, 8, 4, 2};bubbleSort(arr);System.out.println("冒泡排序结果: " + Arrays.toString(arr));int[] arr2 = {5, 3, 8, 4, 2};quickSort(arr2, 0, arr2.length - 1);System.out.println("快速排序结果: " + Arrays.toString(arr2));int target = 4;int index = binarySearch(arr2, target);System.out.println("二分查找结果: " + (index != -1 ? "找到,下标为 " + index : "未找到"));}
}AI写代码java运行
输出:
冒泡排序结果: [2, 3, 4, 5, 8]
快速排序结果: [2, 3, 4, 5, 8]
二分查找结果: 找到,下标为 2AI写代码
通过合理选择排序和查找算法,可以高效地处理数据。
十、异常
1. 异常的基本概念
- 异常在 Java 中以类的方式存在,每个异常类都可以创建异常对象。
- 方法覆盖规则:子类重写父类方法时,不能抛出比父类方法更高的异常(运行时异常
RuntimeException
除外)。 - 异常的分类:
java.lang.Throwable
:异常的父类,有两个子类:Error
:错误,通常是系统级错误(如OutOfMemoryError
),不可处理,只能退出程序。Exception
:异常,所有异常都是在运行阶段发生的。Exception
的直接子类:编译时异常(受检异常CheckedException
),需要在编写程序时预处理。RuntimeException
:运行时异常,通常由程序逻辑错误引起,不需要显式处理。
2. 常见运行时异常
NullPointerException
:空指针异常,尝试访问null
对象的成员。ArrayIndexOutOfBoundsException
:数组下标越界异常。ClassCastException
:类型转换异常,尝试将对象强制转换为不兼容的类型。NumberFormatException
:数字转换异常,尝试将非数字字符串转换为数字。
3. 异常处理方式
-
throws
关键字:-
在方法声明位置使用,将异常抛给调用者处理。
-
示例:
public void readFile() throws IOException {// 可能抛出 IOException 的代码 }AI写代码java运行
-
-
try-catch-finally
语句:-
捕获并处理异常。
-
示例:
try {// 可能抛出异常的代码 } catch (NullPointerException e) {System.out.println("空指针异常: " + e.getMessage()); } catch (ArrayIndexOutOfBoundsException e) {System.out.println("数组下标越界: " + e.getMessage()); } finally {// 无论是否发生异常,都会执行的代码System.out.println("finally 块执行"); }AI写代码java运行
-
4. 常用异常方法
getMessage()
:获取异常的简单描述信息(通常是构造方法的参数)。printStackTrace()
:打印异常的堆栈追踪信息(异步线程中常用)。
5. 自定义异常
-
步骤:
- 编写一个类继承
Exception
(受检异常)或RuntimeException
(运行时异常)。 - 提供两个构造方法:一个无参,一个有参。
- 使用
throw
手动抛出异常。
- 编写一个类继承
-
示例:
// 自定义异常类 public class MyException extends Exception {public MyException() {super();}public MyException(String message) {super(message);} }// 使用自定义异常 public class Test {public static void main(String[] args) {try {throw new MyException("自定义异常发生");} catch (MyException e) {System.out.println(e.getMessage());}} }AI写代码java运行
6. 异常处理的最佳实践
- 明确异常类型:捕获具体异常,而不是直接捕获
Exception
。 - 合理使用
finally
:用于释放资源(如关闭文件、数据库连接等)。 - 避免空指针异常:在使用对象前进行
null
检查。 - 日志记录:使用日志框架(如
Log4j
或SLF4J
)记录异常信息,便于排查问题。
总结
- 异常分类:
Error
和Exception
,其中Exception
分为编译时异常和运行时异常。 - 处理方式:
throws
抛给调用者,try-catch-finally
捕获并处理。 - 自定义异常:继承
Exception
或RuntimeException
,提供构造方法,使用throw
抛出。 - 最佳实践:明确异常类型,合理使用
finally
,避免空指针异常,记录日志。
十一、I/O
I/O(输入/输出)概述
I/O(Input/Output)是指应用程序与外部设备(如磁盘、网络、键盘、显示器等)之间的数据交互。Java通过java.io
包提供了丰富的I/O类库,支持文件操作、字节流、字符流等功能。
File类
File
类是java.io
包中唯一代表磁盘文件本身的对象,用于操作文件和目录。
构造方法
-
File(String path)
:- 根据路径创建
File
对象。
- 根据路径创建
-
File(String parent, String child)
:- 根据父路径和子路径(包括文件名)创建
File
对象。
- 根据父路径和子路径(包括文件名)创建
-
File(File parent, String child)
:- 根据
File
对象表示的父路径和子路径创建File
对象。
- 根据
注意:路径分隔符可以使用\\
(Windows)或/
(Unix/Linux)。
常用方法
-
boolean exists()
:- 判断文件或目录是否存在。
-
boolean delete()
:- 删除文件或目录。
-
boolean createNewFile()
:- 如果文件不存在,则创建一个新文件。
-
String getName()
:- 返回文件或目录的名称。
-
String getPath()
:- 返回文件或目录的路径。
-
String getAbsolutePath()
:- 返回文件或目录的绝对路径。
-
boolean canRead()
:- 判断文件是否可读。
-
boolean canWrite()
:- 判断文件是否可写。
-
boolean isFile()
:- 判断是否为文件。
-
boolean isDirectory()
:- 判断是否为目录。
-
long length()
:- 返回文件内容的长度(字节数)。
-
String[] list()
:- 返回目录内所有文件和子目录的名称。
-
File[] listFiles()
:- 返回目录内所有文件和子目录的
File
对象。
- 返回目录内所有文件和子目录的
-
createTempFile(String prefix, String suffix)
:- 创建临时文件。
-
deleteOnExit()
:- JVM退出时自动删除文件。
字节流
字节流用于处理二进制数据(如图片、音频、视频等),以字节为单位进行读写操作。
字节输入流(InputStream)
InputStream
是字节输入流的抽象类,用于从源(如文件、网络等)读取数据。
常用方法:
-
int read()
:- 逐个字节读取,返回读取的字节值(0-255),如果到达流末尾则返回-1。
-
int read(byte[] b)
:- 将数据读取到字节数组
b
中,返回实际读取的字节数。
- 将数据读取到字节数组
-
int read(byte[] b, int off, int len)
:- 从偏移量
off
开始,读取len
个字节到数组b
中,返回实际读取的字节数。
- 从偏移量
-
void close()
:- 关闭流,释放资源。
字节输出流(OutputStream)
OutputStream
是字节输出流的抽象类,用于将数据写入目标(如文件、网络等)。
常用方法:
-
void write(int b)
:- 逐个字节写入。
-
void write(byte[] b)
:- 将字节数组
b
中的数据写入。
- 将字节数组
-
void write(byte[] b, int off, int len)
:- 从偏移量
off
开始,写入len
个字节。
- 从偏移量
-
void flush()
:- 强制将缓冲区中的数据写入目标。
-
void close()
:- 关闭流,释放资源。
具体实现类
-
FileInputStream
:- 用于从文件中读取字节数据。
-
FileOutputStream
:- 用于将字节数据写入文件。
拓展总结
-
文件操作:
- 使用
File
类可以创建、删除、重命名文件,判断文件是否存在,查询文件属性等。
- 使用
-
字节流:
- 字节流适用于处理二进制数据,
InputStream
和OutputStream
是字节流的抽象基类。 FileInputStream
和FileOutputStream
是常用的字节流实现类,用于文件的读写操作。
- 字节流适用于处理二进制数据,
-
流的使用注意事项:
- 使用流时,务必在操作完成后调用
close()
方法关闭流,释放系统资源。 - 对于输出流,可以调用
flush()
方法强制将缓冲区中的数据写入目标。
- 使用流时,务必在操作完成后调用
-
临时文件:
- 使用
createTempFile()
方法可以创建临时文件,deleteOnExit()
方法可以确保JVM退出时自动删除临时文件。
- 使用
-
路径处理:
- 路径分隔符可以使用
\\
(Windows)或/
(Unix/Linux),Java会自动处理。
- 路径分隔符可以使用
-
性能优化:
- 对于大文件的读写,建议使用缓冲区(如
BufferedInputStream
和BufferedOutputStream
)来提高性能。
- 对于大文件的读写,建议使用缓冲区(如
示例代码
文件操作
File file = new File("test.txt");
if (!file.exists()) {file.createNewFile(); // 创建文件
}
System.out.println("文件名称: " + file.getName());
System.out.println("文件路径: " + file.getAbsolutePath());
file.delete(); // 删除文件AI写代码java运行
字节流读写
// 写入文件
try (FileOutputStream fos = new FileOutputStream("output.txt")) {fos.write("Hello, World!".getBytes());fos.flush();
}// 读取文件
try (FileInputStream fis = new FileInputStream("output.txt")) {byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {System.out.println(new String(buffer, 0, len));}
}AI写代码java运行
通过掌握这些核心概念和类库,可以高效地处理文件操作和字节流读写。
字符流总结
字符流是Java I/O中用于处理文本数据的流,它以字符为单位进行读写操作。与字节流不同,字符流专门用于处理字符数据(如文本文件),并且支持字符编码(如UTF-8、GBK等),能够正确处理多字节字符。
字符流概述
字符流的核心类是Reader
和Writer
,它们分别是字符输入流和字符输出流的抽象基类。字符流的主要特点包括:
- 以字符为单位:
- 字符流以字符为单位读写数据,适合处理文本文件。
- 支持字符编码:
- 字符流可以正确处理字符编码,避免乱码问题。
- 高效读写:
- 字符流通常与缓冲区结合使用(如
BufferedReader
和BufferedWriter
),提高读写效率。
- 字符流通常与缓冲区结合使用(如
字符输入流(Reader)
Reader
是字符输入流的抽象类,用于从源(如文件、字符串等)读取字符数据。
常用方法
-
int read()
:- 读取单个字符,返回字符的Unicode值(0-65535),如果到达流末尾则返回-1。
-
int read(char[] cbuf)
:- 将字符数据读取到字符数组
cbuf
中,返回实际读取的字符数。
- 将字符数据读取到字符数组
-
int read(char[] cbuf, int off, int len)
:- 从偏移量
off
开始,读取len
个字符到数组cbuf
中,返回实际读取的字符数。
- 从偏移量
-
void close()
:- 关闭流,释放资源。
具体实现类
-
FileReader
:- 用于从文件中读取字符数据。
-
BufferedReader
:- 带有缓冲区的字符输入流,提供
readLine()
方法逐行读取文本。
- 带有缓冲区的字符输入流,提供
-
InputStreamReader
:- 将字节流转换为字符流,支持指定字符编码。
字符输出流(Writer)
Writer
是字符输出流的抽象类,用于将字符数据写入目标(如文件、控制台等)。
常用方法
-
void write(int c)
:- 写入单个字符。
-
void write(char[] cbuf)
:- 写入字符数组
cbuf
中的数据。
- 写入字符数组
-
void write(char[] cbuf, int off, int len)
:- 从偏移量
off
开始,写入len
个字符。
- 从偏移量
-
void write(String str)
:- 写入字符串
str
。
- 写入字符串
-
void write(String str, int off, int len)
:- 从偏移量
off
开始,写入len
个字符。
- 从偏移量
-
void flush()
:- 强制将缓冲区中的数据写入目标。
-
void close()
:- 关闭流,释放资源。
具体实现类
-
FileWriter
:- 用于将字符数据写入文件。
-
BufferedWriter
:- 带有缓冲区的字符输出流,提供
newLine()
方法写入换行符。
- 带有缓冲区的字符输出流,提供
-
OutputStreamWriter
:- 将字节流转换为字符流,支持指定字符编码。
字符流与字节流的区别
-
单位不同:
- 字节流以字节为单位,适合处理二进制数据。
- 字符流以字符为单位,适合处理文本数据。
-
编码支持:
- 字节流不涉及字符编码,直接处理字节数据。
- 字符流支持字符编码,能够正确处理多字节字符。
-
性能优化:
- 字符流通常与缓冲区结合使用,提高读写效率。
示例代码
字符流读写文件
// 写入文件
try (FileWriter fw = new FileWriter("output.txt");BufferedWriter bw = new BufferedWriter(fw)) {bw.write("Hello, World!");bw.newLine(); // 写入换行符bw.write("This is a test.");
}// 读取文件
try (FileReader fr = new FileReader("output.txt");BufferedReader br = new BufferedReader(fr)) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}
}AI写代码java运行
使用指定编码读写文件
// 写入文件(指定编码为UTF-8)
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8");BufferedWriter bw = new BufferedWriter(osw)) {bw.write("你好,世界!");
}// 读取文件(指定编码为UTF-8)
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("output.txt"), "UTF-8");BufferedReader br = new BufferedReader(isr)) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}
}AI写代码java运行
总结
-
字符流适用场景:
- 处理文本文件、字符串等字符数据。
-
核心类:
Reader
和Writer
是字符流的抽象基类。FileReader
、BufferedReader
、FileWriter
、BufferedWriter
是常用的实现类。
-
字符编码:
- 使用
InputStreamReader
和OutputStreamWriter
可以指定字符编码,避免乱码问题。
- 使用
-
性能优化:
- 使用
BufferedReader
和BufferedWriter
可以提高读写效率。
- 使用
-
流关闭:
- 使用
try-with-resources
语法确保流被正确关闭,释放资源。
- 使用
通过掌握字符流的核心概念和类库,可以高效地处理文本数据的读写操作。
十二、集合
集合是Java中用于存储和管理一组对象的容器。它提供了一种更灵活、更高效的方式来操作数据集合。以下是集合的核心概念和总结:
集合的特点
-
容器性质:
- 集合是一个容器,可以容纳其他类型的数据。
- 集合不能直接存储基本数据类型(如
int
、char
等),也不能直接存储对象,存储的是Java对象的内存地址(引用)。
-
数据结构:
- 不同的集合对应不同的数据结构(如数组、链表、哈希表、二叉树等)。
- 使用不同的集合等同于使用了不同的数据结构。
-
包位置:
- 所有的集合类都位于
java.util
包中。
- 所有的集合类都位于
集合的层次结构
-
超级父接口:
Iterable<T>
:- 所有集合都是可迭代的,即可以通过迭代器遍历集合中的元素。
- 方法:
Iterator<T> iterator()
:返回集合的迭代器。
-
单个元素集合的父接口:
Collection<E>
:- 表示存储单个元素的集合的超级接口。
- 子接口包括:
List
、Set
、Queue
等。
-
键值对集合的父接口:
Map<K,V>
:- 表示存储键值对的集合,独立于
Collection
体系。
- 表示存储键值对的集合,独立于
集合的实现类总结
1. List
接口的实现类:
ArrayList
:- 底层是数组,查询快,增删慢。
- 非线程安全。
LinkedList
:- 底层是双向链表,增删快,查询慢。
- 非线程安全。
Vector
:- 底层是数组,线程安全,但效率较低,使用较少。
2. Set
接口的实现类:
HashSet
:- 底层是
HashMap
,元素存储在HashMap
的key
部分。 - 无序且不允许重复。
- 底层是
TreeSet
:- 底层是
TreeMap
,元素存储在TreeMap
的key
部分。 - 元素自动按大小顺序排序。
- 底层是
3. Map
接口的实现类:
HashMap
:- 底层是哈希表,非线程安全。
- 允许
null
键和null
值。
Hashtable
:- 底层是哈希表,线程安全,但效率较低,使用较少。
- 不允许
null
键和null
值。
Properties
:- 底层是哈希表,线程安全。
key
和value
只能存储字符串(String
)。
TreeMap
:- 底层是二叉树。
key
自动按照大小顺序排序。
集合的选择
-
需要存储单个元素:
- 如果需要有序且允许重复,使用
List
:- 查询多,增删少:
ArrayList
。 - 增删多,查询少:
LinkedList
。
- 查询多,增删少:
- 如果不需要重复元素,使用
Set
:- 无序:
HashSet
。 - 有序:
TreeSet
。
- 无序:
- 如果需要有序且允许重复,使用
-
需要存储键值对:
- 非线程安全:
HashMap
。 - 线程安全:
Hashtable
或Properties
。 - 需要排序:
TreeMap
。
- 非线程安全:
-
线程安全:
- 如果需要线程安全,可以使用
Vector
、Hashtable
或Properties
,但效率较低。 - 推荐使用
Collections.synchronizedList()
或ConcurrentHashMap
等并发集合。
- 如果需要线程安全,可以使用
总结
-
集合的核心:
- 集合是存储和管理一组对象的容器,存储的是对象的内存地址。
- 不同的集合对应不同的数据结构,选择合适的集合可以提高程序效率。
-
常用集合:
List
:有序且允许重复,常用ArrayList
和LinkedList
。Set
:无序且不允许重复,常用HashSet
和TreeSet
。Map
:存储键值对,常用HashMap
、TreeMap
和Properties
。
-
线程安全:
- 线程安全的集合有
Vector
、Hashtable
和Properties
,但效率较低。 - 推荐使用并发集合(如
ConcurrentHashMap
)来实现线程安全。
- 线程安全的集合有
通过掌握集合的核心概念和常用实现类,可以更高效地处理数据集合,并根据需求选择合适的集合类型。
List 集合存储元素的特点:
有序可重复
有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
可重复:存进去1,可以再存储一个1
Set 集合存储元素的特点(Map的Key):
无序不可重复
无序:存进去的顺序和取出的顺序不一定相同,另外 Set 集合中元素没有下标(哈希表的存储)
不可重复:存进去1,不能再存储1了(哈希表的覆盖)
SortedSet( SortedMap )集合存储元素特点:
首先是无序不可重复的,但是 SortedSet 集合中的元素是可排序的
无序:存进去的顺序和取出的顺序不一定相同,另外 Set 集合中元素没有下标
不可重复:存进去1,不能再存储1了
可排序:可以按照大小顺序排列。
Map 集合的 key ,就是一个 Set 集合。
往 Set 集合中放数据,实际上放到了 Map 集合的 key 部分。
Interface Collection
没有使用泛型前可以存储Object的所有子类型
- Boolean add(E e) 添加元素
- Object[] toArray() 转化成数组(使用不多)
- Int size() 返回此集合中元素的数目。
- Boolean contains(Object o) 如果此集合包含指定的元素(存放在集合中的类型,需要重写equals方法)
- Void clear() 从此集合中删除所有元素
- Boolean equals(Object o) 将指定的对象与此集合进行比较以实现相等性(内存地址)
- Boolean remove(Object o) 从此集合中删除指定元素的单个实例
- Boolean isEmpty() 如果此集合不包含任何元素(判空)则返回。true
Iterator<E> iterator() ***:**不管存进去什么,拿出来都是Object,取出来还是原类型
返回此集合中元素的迭代器**,Collection通用,Map集合不能用**
只要集合结构发生改变迭代器一定要重新获取
- default void forEachRemaining(Consumer<? super E> action) 对每个剩余元素执行给定的操作,直到所有元素都已处理完毕或该操作引发异常。
- Boolean hasNext() 如果迭代具有更多元素,则返回。true
- Object next() 返回迭代中的下一个元素。(返回object)
- default void remove() 从基础集合中删除此迭代器返回的最后一个元素(可选操作)。
Interface List 有序可重复,Collection子接口
- void add(int index, E element) 在此列表中的指定位置插入指定的元素
- E get(int index) 返回此列表中指定位置处的元素
- E set(int index, E element) 将此列表中指定位置的元素替换为指定的元素
- int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含该元素,则返回 -1
- int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含该元素,则返回 -1。
- E remove(int index) 删除此列表中指定位置的元素
Class ArrayList 非线程安全数组,初始化容量10,底层object数组
构造方法:
- ArrayList() 构造初始容量为 10 的空列表(底层先创建了一个长度为0的数组,添加元素是初始化为10,自动扩容1.5倍)
- ArrayList(int initialCapacity) 构造具有指定初始容量的空列表(建议提前估计,减少扩容)
- ArrayList(Collection<? extends E> c) 构造一个列表,其中包含指定集合的元素,并按集合的迭代器返回这些元素的顺序排列。
方法:同List方法
Class LinkedList 双向链表,随机增删效率高,检索效率低
Class Vector 线程安全数组,默认10,扩容翻倍**(不经常使用)**
转换:使用集合工具类:java.util.Collections.synchronizedList(集合)
Interface Set 无序不可重复 存储Map的Key
Class HashSet 哈希表(底层HashMap)
需要重写hashCode和equals方法,其他方法参见HashMap
Interface SortedSet 无序不可重复可排序
Class TreeSet 二叉树(底层TreeMap Key部分)无序不可重复可排序
Key值自定义类需要实现java.long.Comparable接口或者创建比较器对象
class user implements Comparable<user>{ //自定义类需要实现接口int age;public user(int age) {this.age = age;}@Overridepublic String toString() {return "user{" + "age=" + age + '}';}@Override //重写比较规则public int compareTo(user o) {return this.age-o.age; //返回==0,value覆盖,返回大于0 到右子树,返回小于0到左子树}
}AI写代码java运行
Interface Map<K,V> Map主接口(和Collection没有继承关系)
以Key和Value存储数据都是引用数据类型,都存储内存地址,Key是主导
- V put(K key, V value) 添加键值对(Key元素需要重新hashCode和equals方法)(Key可以为空,只有一个)
- void clear() 清空Map集合
- V get(Object key) 通过key获取value(key元素需要重新hashCode和equals方法)
- boolean containsKey(Object key) 判断Map是否包含某个key(底层equals)
- boolean containsValue(Object value) 判断Map是否包含某个value(底层equals)
- boolean isEmpty() 判断Map集合元素个数是否为零
- Set<K> keySet() 获取Map集合所有的Key(是个set集合)
- V remove(Object key) 通过key删除键值对
- Collection<V> values() 获取Map集合中键值对所有value(返回Collection)
- int size() 获取Map集合所有的键值对个数
Set<Map.Entry<Integer,String>>set1=m.entrySet(); //使用方法
Iterator<Map.Entry<Integer,String>> it=set1.iterator(); //获取迭代器
while (it.hasNext()) {Map.Entry<Integer, String> entry = it.next();System.out.println(entry); //直接遍历Integer key = entry.getKey(); //获取键String value = entry.getValue(); //获取值System.out.println(key + "=" + value); //分开遍历for(Map.Entry<Integer,String> node:set1) //效率较高,适合大数据,直接获取System.out.println(node); //组合遍历AI写代码java运行
Class HashMap<K,V> 哈希表 非线程安全(初始化容量16[必须是2的倍数],默认加载因子0.75)
Key元素类型需要重新hashCode和equals方法
JDK8新特性:当单向链表长度超过8后数据结构会变成红黑树数据结构,当红黑树小于6,会变回链表
构造 函数 描述
- HashMap() 使用默认初始容量 (16) ,默认负载系数 (0.75)
- HashMap(int initialCapacity) 指定的初始容量,默认负载系数 初始容量必须是2的倍数:达到散列均匀,提高存取效率
- HashMap(int initialCapacity, float loadFactor) 指定初始容量和负载系数
- HashMap(Map<? extends K,? extends V> m)
Class Hashtable<K,V> 哈希表 线程安全(synchronized) Key不可以为空*(不常用)**
初始化容量11,默认加载因子0.75f,扩容:原容量*2+1
Class Properties 属性类 继承Hashtable类 仅支持String
- Object setProperty(String key, String value) 存
- String getProperty(String key) 取
- String getProperty(String key, String defaultValue) 当key值为NULL时,返回def的值;当key值不为NULL时,返回key的值
Interface SortedMap<K,V>
Class TreeMap<K,V> 二叉树 可排序集合(中序遍历)
Key值自定义类需要实现java.long.Comparable接口或者创建比较器对象(类或者匿名内部类)
Class Collections 集合工具类
- synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全)映射。
- synchronizedList(List list) 返回由指定列表支持的同步(线程安全)列表。**
**synchronizedCollection(Collection c) 返回由指定集合支持的同步(线程安全)集合 - sort(List list, Comparator<? super T> c) 根据指定比较器引发的顺序对指定列表进行排序。
十三、泛型
1. 泛型概述
-
引入时间:JDK 5.0 之后的新特性。
-
作用:
- 统一集合中元素的类型,避免类型转换错误。
- 只在程序编译阶段起作用,编译后会进行类型擦除(Type Erasure)。
-
语法:
-
在创建对象时,前后两段添加泛型类型。
-
示例:
List<String> list = new ArrayList<String>();AI写代码java运行
-
2. 泛型的优点
- 类型安全:编译时检查类型,避免运行时类型转换错误。
- 代码复用:可以编写通用的类和方法,适用于多种类型。
- 代码简洁:减少强制类型转换的代码。
3. 泛型的缺点
- 导致集合存储缺少多样性:泛型限制了集合中元素的类型,无法存储多种类型的对象。
- 类型擦除:泛型信息在编译后会被擦除,运行时无法获取泛型的具体类型。
4. 自动推断机制(钻石表达式)
-
引入时间:JDK 7 新特性。
-
作用:自动推断泛型类型,简化代码。
-
语法:只写前面的泛型类型,后面的泛型类型可以省略。
-
示例:
List<String> list = new ArrayList<>();AI写代码java运行
5. 自定义泛型
-
泛型类:
-
在定义类时添加
<T>
,T
是类型参数。 -
示例:
public class Box<T> {private T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;} }AI写代码java运行
-
使用:
Box<String> box = new Box<>(); box.setValue("Hello"); String value = box.getValue();AI写代码java运行
-
-
泛型方法:
-
在定义方法时添加
<T>
,T
是类型参数。 -
示例:
public <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);} }AI写代码java运行
-
使用:
Integer[] intArray = {1, 2, 3}; printArray(intArray);AI写代码java运行
-
6. 泛型的通配符
-
<?>
:表示任意类型。 -
<? extends T>
:表示T
或其子类型(上界通配符)。 -
<? super T>
:表示T
或其父类型(下界通配符)。 -
示例:
public void printList(List<?> list) {for (Object element : list) {System.out.println(element);} }AI写代码java运行
7. 泛型的限制
- 不能使用基本类型:泛型类型必须是引用类型(如
Integer
而不是int
)。 - 不能创建泛型数组:例如
new T[10]
是非法的。 - 不能实例化泛型类型:例如
new T()
是非法的。
8. 泛型的应用场景
- 集合框架:如
List<T>
、Map<K, V>
等。 - 工具类:如
Comparator<T>
、Comparable<T>
等。 - 自定义数据结构:如栈、队列、链表等。
总结与拓展
- 泛型的作用:统一集合中元素的类型,提高代码的安全性和复用性。
- 自动推断机制:JDK 7 引入的钻石表达式简化了泛型代码。
- 自定义泛型:通过泛型类和泛型方法实现通用代码。
- 通配符:
<?>
、<? extends T>
、<? super T>
提供了更灵活的类型约束。 - 限制:泛型不能使用基本类型、不能创建泛型数组、不能实例化泛型类型。
十四、多线程
进程是:一个应用程序(1个进程是一个软件)
独立性:系统分配资源和调度资源的独立单位
动态性:进程实质是程序的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
线程是:一个进程中的执行场景/执行单元,是进程中单个顺序控制流,是一条执行路径。
并行:同一时刻,多个指令在多个CPU上同时执行
并发:同一时刻,多个指令在单个CPU交替执行
线程状态转换
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程构造方法:
构造方法名 | 备注 |
---|---|
Thread() | |
Thread(String name) | name为线程名字 |
创建线程第二种方式 | |
Thread(Runnable target) | |
Thread(Runnable target, String name) | name为线程名字 |
Java 中实现线程的三种方式总结
1. 继承 Thread
类
-
实现方式:
- 编写一个类,直接继承
java.lang.Thread
。 - 重写
run()
方法,定义线程执行的任务。
- 编写一个类,直接继承
-
创建线程对象:
MyThread thread = new MyThread();AI写代码java运行
-
启动线程:
thread.start();AI写代码java运行
-
特点:
- 简单易用,但 Java 是单继承,继承
Thread
类后无法继承其他类。
- 简单易用,但 Java 是单继承,继承
2. 实现 Runnable
接口
-
实现方式:
- 编写一个类,实现
java.lang.Runnable
接口。 - 实现
run()
方法,定义线程执行的任务。 - 通常使用匿名内部类创建。
- 编写一个类,实现
-
创建线程对象:
Runnable task = new MyRunnable(); Thread thread = new Thread(task);AI写代码java运行
-
启动线程:
thread.start();AI写代码java运行
-
特点:
- 更灵活,可以避免单继承的限制。
- 适合多个线程共享同一个任务。
3. 使用 Callable
和 Future
接口
-
实现方式:
- 编写一个类,实现
java.util.concurrent.Callable
接口。 - 实现
call()
方法,定义线程执行的任务,并返回结果。
- 编写一个类,实现
-
创建线程对象:
-
创建
Callable
实现类的实例:Callable<Integer> task = new MyCallable();AI写代码java运行
-
使用
FutureTask
包装Callable
对象:FutureTask<Integer> futureTask = new FutureTask<>(task);AI写代码java运行
-
使用
FutureTask
对象作为Thread
的target
创建线程:Thread thread = new Thread(futureTask);AI写代码java运行
-
-
启动线程:
thread.start();AI写代码java运行
-
获取结果:
Integer result = futureTask.get(); // 阻塞直到获取结果AI写代码java运行
-
特点:
call()
方法可以有返回值和抛出异常。- 适合需要获取线程执行结果的场景。
Future
接口的常用方法
cancel(boolean mayInterruptIfRunning)
:尝试取消任务。get()
:获取任务结果,阻塞直到任务完成。get(long timeout, TimeUnit unit)
:在指定时间内获取任务结果,超时抛出TimeoutException
。isCancelled()
:判断任务是否被取消。isDone()
:判断任务是否完成。
三种方式的对比
方式 | 优点 | 缺点 |
---|---|---|
继承 Thread 类 | 简单易用 | 单继承限制,无法继承其他类 |
实现 Runnable 接口 | 灵活,避免单继承限制,适合多线程共享任务 | 无法直接获取线程执行结果 |
使用 Callable 和 Future | 可以获取线程执行结果,支持异常处理,功能更强大 | 使用稍复杂,需要 FutureTask 包装 |
总结
- 继承
Thread
类:适合简单的线程任务,但受限于单继承。 - 实现
Runnable
接口:更灵活,适合多线程共享任务。 - 使用
Callable
和Future
:适合需要获取线程执行结果或处理异常的场景。
根据具体需求选择合适的方式实现多线程编程。
获取当前线程对象、获取线程对象名字、修改线程对象名字
方法名 | 作用 |
---|---|
static Thread currentThread() | 获取当前线程对象 |
String getName() | 获取线程对象名字 |
void setName(String name) | 修改线程对象名字 |
关于线程的sleep方法
方法名 | 作用 |
---|---|
static void sleep(long millis) | 让当前线程休眠millis秒 |
关于线程中断sleep()的方法
方法名 | 作用 |
---|---|
void interrupt() | 终止线程的睡眠 |
Java进程的优先级
常量名 | 备注 |
---|---|
static int MAX_PRIORITY | 最高优先级(10) |
static int MIN_PRIORITY | 最低优先级(1) |
static int NORM_PRIORITY | 默认优先级(5) |
方法:
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
static void yield() | 让位,当前线程暂停,回到就绪状态,让给其它线程。 |
void join() | 将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束 |
void join(long millis) | 接上条,等待该线程终止的时间最长为 millis 毫秒 |
void join(long millis, int nanos) | 接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒 |
多线程并发环境下,数据的安全问题(重点)
1.为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是: 你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:★★★★★)
2.什么时候数据在多线程并发的环境下会存在安全问题呢?★★★★★
满足三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
3.怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。
这种机制被称为:线程同步机制。专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会 牺牲一部分效率 ,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
死锁(DeadLock)
死锁(Deadlock)是多线程编程中的一种常见问题,指的是两个或多个线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。
死锁代码要会写。一般面试官要求你会写。只有会写的,才会在以后的开发中注意这个事儿。因为死锁很难调试。
死锁的四个必要条件
死锁的发生必须同时满足以下四个条件:
-
互斥条件(Mutual Exclusion):
- 资源一次只能被一个线程占用。
-
占有并等待(Hold and Wait):
- 线程已经占有了至少一个资源,但又申请新的资源,而新的资源被其他线程占用。
-
不可抢占(No Preemption):
- 线程已占有的资源不能被其他线程强行抢占,必须由线程自己释放。
-
循环等待(Circular Wait):
- 存在一个线程的等待循环链,每个线程都在等待下一个线程所占用的资源。
Java 中的死锁示例
以下是一个经典的死锁代码示例,展示了两个线程互相等待对方释放锁的情况:
public class DeadlockExample {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread 1: Holding lock 1...");try {Thread.sleep(100); // 模拟操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1: Waiting for lock 2...");synchronized (lock2) {System.out.println("Thread 1: Acquired lock 2!");}}});Thread thread2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread 2: Holding lock 2...");try {Thread.sleep(100); // 模拟操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2: Waiting for lock 1...");synchronized (lock1) {System.out.println("Thread 2: Acquired lock 1!");}}});thread1.start();thread2.start();}
}AI写代码java运行
代码分析
-
线程1:
- 先获取
lock1
,然后尝试获取lock2
。 - 在获取
lock2
之前,线程1会休眠100毫秒。
- 先获取
-
线程2:
- 先获取
lock2
,然后尝试获取lock1
。 - 在获取
lock1
之前,线程2会休眠100毫秒。
- 先获取
-
死锁发生:
- 线程1持有
lock1
并等待lock2
。 - 线程2持有
lock2
并等待lock1
。 - 两个线程互相等待,导致死锁。
- 线程1持有
如何避免死锁
-
避免嵌套锁:
- 尽量不要在持有一个锁的同时去申请另一个锁。
-
按顺序获取锁:
- 如果多个线程需要获取多个锁,确保它们以相同的顺序获取锁。
-
使用超时机制:
- 在获取锁时设置超时时间,如果超时则释放已持有的锁并重试。
-
使用工具检测:
- 使用工具(如
jstack
)检测死锁。
- 使用工具(如
死锁的调试与检测
-
使用
jstack
:- 运行程序后,使用
jstack
命令查看线程状态,可以检测到死锁。
- 运行程序后,使用
-
日志输出:
- 在代码中添加日志,记录锁的获取和释放情况。
-
使用工具:
- 使用IDE(如IntelliJ IDEA)或第三方工具(如VisualVM)检测死锁。
守护线程
在Java中,线程分为两大类:用户线程和守护线程。守护线程(Daemon Thread)是一种特殊的线程,它的生命周期依赖于用户线程。当所有的用户线程结束时,守护线程会自动退出。
守护线程的特点
-
依赖用户线程:
- 守护线程是为用户线程提供服务的线程。
- 当所有的用户线程结束时,守护线程会自动退出。
-
典型代表:
- 垃圾回收线程(
GC
)是Java中最典型的守护线程。
- 垃圾回收线程(
-
主线程是用户线程:
main
方法所在的线程是用户线程。
-
死循环:
- 守护线程通常是一个死循环,持续执行某些后台任务。
守护线程的应用场景
-
定时任务:
- 例如,每天00:00自动备份系统数据。
- 可以使用定时器(如
Timer
或ScheduledExecutorService
),并将定时任务设置为守护线程。
-
后台监控:
- 例如,监控系统资源使用情况、日志清理等。
-
垃圾回收:
- Java的垃圾回收线程就是一个守护线程。
守护线程的设置
在Java中,可以通过setDaemon(boolean on)
方法将一个线程设置为守护线程:
方法签名 | 说明 |
---|---|
void setDaemon(boolean on) | on 为true 表示将线程设置为守护线程 |
注意:
- 必须在调用
start()
方法之前设置守护线程,否则会抛出IllegalThreadStateException
。 - 守护线程中创建的子线程默认也是守护线程。
代码示例
以下是一个守护线程的示例,展示了如何设置守护线程以及它的行为:
public class DaemonThreadExample {public static void main(String[] args) {Thread daemonThread = new Thread(() -> {while (true) {System.out.println("守护线程正在运行...");try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();}}});// 设置为守护线程daemonThread.setDaemon(true);// 启动守护线程daemonThread.start();// 主线程(用户线程)执行任务System.out.println("主线程开始执行...");try {Thread.sleep(5000); // 模拟主线程执行任务} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程执行完毕,程序退出。");}
}AI写代码java运行
代码分析
-
守护线程:
- 守护线程是一个死循环,每隔1秒输出一条消息。
- 设置为守护线程后,当主线程结束时,守护线程会自动退出。
-
主线程:
- 主线程执行5秒后结束。
- 主线程结束后,守护线程也会自动退出。
守护线程的注意事项
-
资源释放:
- 守护线程中不要执行关键任务(如文件写入、数据库操作等),因为它的退出是不可控的。
-
线程优先级:
- 守护线程的优先级通常较低,适合执行后台任务。
-
生命周期:
- 守护线程的生命周期依赖于用户线程,不能独立存在。
定时器的作用:
间隔特定的时间,执行特定的程序。在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
构造方法名 | 备注 |
---|---|
Timer() | 创建一个定时器 |
Timer(boolean isDaemon) | isDaemon为true为守护线程定时器 |
Timer(String name) | 创建一个定时器,其线程名字为name |
Timer(String name, boolean isDaemon) | 结合2、3 |
方法名 | 作用 |
void schedule(TimerTask task, Date firstTime, long period) | 安排指定的任务在指定的时间开始进行重复的固定延迟执行 |
void cancel() | 终止定时器 |
关于Object类的wait()、notify()、notifyAll()方法
方法名 | 作用 |
---|---|
void wait() | 让活动在当前对象的线程无限等待(释放之前占有的锁) |
void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
void notifyAll() | 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁) |
wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是 Object类中自带 的。
wait方法和notify方法不是通过线程对象调用
调用:
Object o = new Object();
o.wait();
总结 ★★★★★(呼应生产者消费者模式)
1、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
2、wait方法和notify方法建立在 线程同步 的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
3、wait方法作用:o.wait() 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
4、notify方法作用:o.notify() 让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
生产者消费者模式(wait()和notify())
什么是“生产者和消费者模式”?
生产线程负责生产,消费线程负责消费。
生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
模拟一个业务需求
仓库我们采用List集合。
List集合中假设只能存储1个元素。
1个元素就表示仓库满了。
如果List集合中元素个数是0,就表示仓库空了。
保证List集合中永远都是最多存储1个元素。
必须做到这种效果:生产1个消费1个。
十五、反射
1. Class 对象概述
- Class 对象:在 Java 中,每个类在加载到内存时都会生成一个
Class
对象,该对象存储了类的所有信息(如方法、构造函数、字段等)。 - 反射:通过
Class
对象,可以在运行时动态获取类的信息并操作类的成员(如调用方法、访问字段等)。
2. Class 对象的生成方式
-
类名.class
:-
JVM 将类加载到内存中,但不进行初始化。
-
返回该类的
Class
对象。 -
示例:
Class<?> clazz = String.class;AI写代码java运行
-
-
Class.forName("包名.类名")
:-
加载类并默认进行静态初始化。
-
返回该类的
Class
对象。 -
示例:
Class<?> clazz = Class.forName("java.lang.String");AI写代码java运行
-
-
Class.forName("包名.类名", false, 类加载器)
:-
第二个参数为
false
时,不进行初始化;为true
时,进行初始化。 -
示例:
Class<?> clazz = Class.forName("java.lang.String", false, ClassLoader.getSystemClassLoader());AI写代码java运行
-
-
实例对象.getClass()
:-
对类进行静态初始化和非静态初始化。
-
返回运行时实际对象所属类的
Class
对象。 -
示例:
String str = "Hello"; Class<?> clazz = str.getClass();AI写代码java运行
-
3. Class 对象的特性
- 父子类 Class 对象不一致:
- 如果
A
是B
的子类,则A.class
和B.class
返回的Class
对象不同。 - 如果
a
是A
的实例,则A.class
和a.getClass()
返回的Class
对象一致。
- 如果
4. Class 类的常用方法
getName()
:返回类的全限定名(包名 + 类名)。getSuperclass()
:返回类的直接父类的Class
对象。getInterfaces()
:返回类实现的所有接口的Class
数组。isArray()
:判断该类是否是数组类型。isEnum()
:判断该类是否是枚举类型。isInterface()
:判断该类是否是接口。isPrimitive()
:判断该类是否是基本类型(如int
、boolean
等)。isAssignableFrom(Class cls)
:判断该类是否是cls
的父类或父接口。getComponentType()
:如果该类是数组类型,返回数组的组件类型。asSubclass(Class clazz)
:将当前Class
对象转换为clazz
的子类类型。
5. asSubclass
方法的使用
-
作用:将当前
Class
对象转换为指定类的子类类型。 -
示例:
List<String> strList = new ArrayList<>(); Class<? extends List> strListCast = strList.getClass().asSubclass(List.class);AI写代码java运行
-
动态加载时的应用:
Class.forName("xxx.xxx.xxx").asSubclass(List.class).newInstance();AI写代码java运行
- 如果
xxx.xxx.xxx
是List
的子类,则正常执行;否则抛出ClassCastException
。
- 如果
6. 静态加载与动态加载
- 静态加载:通过
new ClassName()
加载类,编译时必须提供类的定义。 - 动态加载:通过
Class.forName("ClassName")
加载类,编译时可以缺席,运行时按需提供。
总结
- Class 对象:存储类的所有信息,是反射机制的核心。
- 生成方式:
类名.class
、Class.forName()
、实例对象.getClass()
。 - 常用方法:
getName()
、getSuperclass()
、getInterfaces()
、asSubclass()
等。 asSubclass
:用于将Class
对象转换为指定类的子类类型。- 静态加载与动态加载:静态加载在编译时提供类定义,动态加载在运行时按需提供。
通过掌握 Class
对象和反射机制,可以在运行时动态操作类的成员,实现灵活的编程。
十六、小游戏(进击的小鸟)
public class StartGame { //游戏开始类public static void main(String[] args) throws InterruptedException {JFrame jFrame = new JFrame("进击の小鸟"); //创建窗口对象jFrame.setSize(400,600);//窗口大小jFrame.setLocationRelativeTo(null); //窗口相对位置jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设定点击关闭结束程序 BirdGame birdGame = new BirdGame(); //初始化游戏对象类
jFrame.add(birdGame); //把创建好的对象加进来
jFrame.setVisible(true); //让窗口可视化
birdGame.action(); //地面运动方法}
}AI写代码java运行
public class Bird {public BufferedImage images[];public BufferedImage image; //存放小鸟图片public int x;public int y;public int width;public int height;public int index=0;public double speed=0; //小鸟初始速度public double upspeed=30; //初始上抛速度public double s=0; //经过t,发生的位移public double t=0.2; //发生位移时间public double g=9.8; //重力加速度public Bird() throws IOException {x=120;y=120;images=new BufferedImage[8];image= ImageIO.read(getClass().getResource("0.png"));width=image.getWidth();height=image.getHeight();for (int i=0;i<images.length;i++) {images[i] = ImageIO.read(getClass().getResource(i+".png"));}}public void fly(){ //小鸟飞飞index++;image=images[index/2%8];}public void upSpeed(){ //鼠标点击游戏屏幕,给小鸟一个初始上抛速度speed=upspeed;}public void distanceChange(){ //实现小鸟速度,位移,纵坐标变化double v=speed; //初始速度s=v*t-g*t*t/2; //经过t小鸟的位移speed=v-g*t; //小鸟经过时间t的末速度y=y-(int)s; //经过时间t后,小鸟的y}
}AI写代码java运行
public class Column { //管道类public BufferedImage cImage;public int x;public int y;public int width;public int height;public int distance=270; //两根管道之间的距离public static int count=0;Random random = new Random();public Column() throws IOException {cImage= ImageIO.read(getClass().getResource("column.png"));x=450+distance*count;width=cImage.getWidth(); //获得管道的宽height=cImage.getHeight(); //高y=-( height/2-random.nextInt(300)-50);count++;}public void step(){x-=5; //让地面往左运动if (x<=-width/2){x=x+distance*2;y=-(height/2-random.nextInt(300)-50) ;//x=400;}}
}AI写代码java运行
public class Ground { //地面类public BufferedImage image; //存放地面图片public int x;public int y;public Ground() { try {
x=0;
y=500;
image= ImageIO.read(getClass().getResource("ground.png"));
} catch (IOException e) {
e.printStackTrace();
}}public void step(){
x-=1; //让地面往左运动
if (x==-100){
x=0;
}}
}AI写代码java运行
public class Music implements Runnable { //音乐类Player player=null;@Overridepublic void run() {InputStream resourceAsStream = this.getClass().getResourceAsStream("2.mp3");try {player=new Player(resourceAsStream);player.play();} catch (JavaLayerException e) {e.printStackTrace();}}public void stopBGM(){if (player!=null)player.close();}
}AI写代码java运行
public class Score { //连接对象private String sid;private int score;private String time;public String getSid() {return sid;}public void setSid(String sid) {this.sid = sid;}public int getScore() {return score;}public void setScore(int score) {this.score = score;}public String getTime() {return time;}public void setTime(String time) {this.time = time;}
}AI写代码java运行
public class ScoreManager { //jdbc连接static{try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}}public List<Score> selectAllScore(){ //查询方法List<Score> list = new ArrayList<>(); try {
String sql="select * from score order by time";
Connection conn = DriverManager.getConnection("jdbc:mysql://cdb-kthncrwi.bj.tencentcdb.com:10159/flybird?useUnicode=true", "student", "521qianfeng");
PreparedStatement pst = conn.prepareStatement(sql);
ResultSet resultSet = pst.executeQuery();
while (resultSet.next()){
Score score = new Score();
score.setSid(resultSet.getString("sid"));
score.setScore(resultSet.getInt("score"));
score.setTime(resultSet.getString("time"));
list.add(score);
}
} catch (SQLException e) {
e.printStackTrace();
} return list;}public int insertScore(int score) { //插入方法
int num = 0;
String sql = "insert into score(sid,score,time) value(?,?,?)";
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://cdb-kthncrwi.bj.tencentcdb.com:10159/flybird?useUnicode=true", "student", "521qianfeng");
PreparedStatement pst = conn.prepareStatement(sql);
String sid= UUID.randomUUID().toString(); //随机生成id
pst.setString(1,sid);
pst.setInt(2,score);
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //创建时间类型对象
String time=simpleDateFormat.format(date);
pst.setString(3,time);
num=pst.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return num;}
}AI写代码java运行
public class BirdGame extends JPanel { //自定义面板类继承面板类ScoreManager sc=new ScoreManager();public JPanel jp=new JPanel();public BufferedImage bg; //图片缓冲区(在显示图片前对图片进行操作 eg:.getWidth()宽,.getHeight()高)public BufferedImage startbg;public BufferedImage overbg;public Ground ground;public Bird bird;public Column columns[];public Music music;String file="H:\\Java程序\\小程序\\src\\小鸟\\png\\bg.png";public int state; //表示游戏状态public static final int START=0; //开始public static final int RUNNING=1; //运行public static final int GAMEOVER=2; //结束public static int score=0; //初始积分public BirdGame(){try {state=START; //游戏初始为游戏开始状态ground=new Ground(); //创建地面类对象,调用地面类构造方法bird = new Bird();columns=new Column[2];music = new Music();for (int i=0;i<columns.length;i++){columns[i]=new Column();}bg= ImageIO.read(getClass().getResource("bg.png")); //读取这张图片并把图片值赋给变量//bg=ImageIO.read(new File(file));//bg=ImageIO.read(new File("src/小鸟/png/bg.png"));startbg=ImageIO.read(getClass().getResource("start.png"));overbg=ImageIO.read(getClass().getResource("gameover.png"));} catch (IOException e) {e.printStackTrace();}}@Overridepublic void paint(Graphics g) { //绘制一次的画画方法super.paint(g); //调用画笔g.drawImage(bg,0,0,null); //绘制背景(最后一个参数为观察者switch (state){case START://绘制游戏开始图片settishi(g);g.drawImage(startbg,0,0,null); break;
case RUNNING:
for (int i=0;i<columns.length;i++) {
g.drawImage(columns[i].cImage, columns[i].x, columns[i].y, null);
}
break;
case GAMEOVER:
//绘制游戏结束图片
settishi2(g);
g.drawImage(overbg,0,0,null); break; }
g.drawImage(ground.image,ground.x,ground.y,null); //绘制地面
g.drawImage(bird.image,bird.x, bird.y,null); //绘制小鸟 setScore(g);}public boolean isHitGround(){ //撞击地面
if (bird.y+bird.height>500){
return true;
}else {
return false;
}}public boolean isHitSky(){ //撞击天空
if (bird.y<0){
return true;
}else {
return false;
}}public boolean isguandao(Column c) {
if (bird.x + bird.width >= c.x && c.x + c.width >= bird.x) { //撞击管道左右
if (bird.y <= c.height / 2 + c.y - 72 || bird.y + bird.height >= c.height / 2 + c.y + 72) {
return true;
} else {
return false;
} } else {
return false;
}}public void setScore(Graphics g){ //绘制分数方法Font font = new Font(Font.SERIF, Font.ITALIC, 40); //罗马字体,斜体,40号g.setFont(font); //获取字体g.setColor(Color.white);//获取颜色g.drawString(score+"分",40,60); //画字符串}public void settishi(Graphics g){ //绘制分数方法Font font1 = new Font(Font.SERIF, Font.BOLD, 25); //罗马字体,斜体,40号g.setFont(font1); //获取字体g.setColor(Color.black);//获取颜色g.drawString("点击屏幕开始运行",110,400); //画字符串g.drawString(" 制作人---赵嘉盟",120,430);}public void settishi2(Graphics g){ //绘制分数方法Font font2 = new Font(Font.SANS_SERIF, Font.BOLD, 30); //罗马字体,斜体,40号g.setFont(font2); //获取字体g.setColor(Color.red);//获取颜色g.drawString("点击屏幕重新开始",100,500); //画字符串}public void action() throws InterruptedException { //游戏对象运动方法this.addMouseListener(new BirdMouseListener()); //添加鼠标监听器 while (true){
switch (state){ //状态不同,对象运动效果不同
case START:
ground.step(); //调用地面运动方法
bird.fly();break;case RUNNING: bird.distanceChange();
ground.step(); //调用地面运动方法
bird.fly(); if (isHitGround()||isHitSky()){
state=GAMEOVER;
break;
}
for (int i=0;i<columns.length;i++){
Column cl=columns[i];
cl.step();
if (isguandao(cl)){
state=GAMEOVER;
break;
}
if (bird.x==cl.x){
score++;
}
} break;
case GAMEOVER:
music.stopBGM();
break;
}
repaint(); //刷新方法(重新绘制)
Thread.sleep(50); //线程睡眠
}}class BirdMouseListener extends MouseAdapter{ //小鸟飞行鼠标控制监听内部类
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e); switch (state){
case START:
state=RUNNING; //鼠标点击开始运行
Thread thread = new Thread(music);
thread.start();
break;
case RUNNING:
bird.upSpeed(); //鼠标点击屏幕给小鸟一个初始上抛速度
break;
case GAMEOVER:
sc.insertScore(score); //向数据库插入分数
List<Score> scores = sc.selectAllScore();//查询数据库所有分数
String message="";
for (Score score1 : scores) {
message=message+"时间:"+score1.getTime()+"\n分数:"+score1.getScore()+"\n";
}
JOptionPane.showConfirmDialog(jp,message,"实时分数",JOptionPane.WARNING_MESSAGE);
state=START; //鼠标点击游戏恢复开始状态 bird.x=120;
bird.y=220;
bird.speed=0;
Column.count=0;
try {
columns[0] = new Column();
} catch (IOException ex) {
ex.printStackTrace();
}
try {
columns[1] = new Column();
} catch (IOException ex) {
ex.printStackTrace();
}
score = 0;//给积分初始化
for (int i=0;i<columns.length;i++){
try {
columns[i]=new Column();
} catch (IOException ex) {
ex.printStackTrace();
}
} break; }
}}
}AI写代码java运行
十七、Stream
Stream简介
Java 8 中的 Stream 是对(Collection)集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作
或大批量数据操作。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。
Stream原理
这种编程风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的
结果。
Stream优点
(1)速度更快
(2)代码更少(增加了新的语法Lambda表达式)
(3)强大的Stream API
(4)便于并行
(5)最大化减少了空指针异常Optional
Stream的操作三个步骤:
(1)创建Stream,一个数据源(如:集合、数组),获取一个流;
(2)中间操作,一个中间操作链,对数据源的数据进行处理;
(3)终止操作,一个终止操作,执行中间操作链,并产生结果。
集合有两种方式生成流:
stream() − 为集合创建串行流。
parallelStream() − 为集合创建并行流
-Stream的的中间操作(intermediate)和最终操作(terminal)都包含的方法:
中间操作(intermediate)
1.filter : 通过设置条件来过滤元素。
List<String> list = Arrays.asList("aaa","ddd","bbb","ccc","a2a","d2d","b2b","c2c","a3a","d3d","b3b","c3c");list.stream().filter((s)->s.contains("a")).forEach(s -> System.out.println(s));AI写代码java运行
以上代码使用filter方法过滤出只包含”a”的元素,然后通过forEach将满足条件的元素遍历出来。
map : 就是将对应的元素使用给定方法进行转换。
List<String> list = Arrays.asList("aaa","ddd","bbb","ccc","a2a","d2d","b2b","c2c","a3a","d3d","b3b","c3c");list.stream().filter((s)->s.contains("a")).map((s)-> s + "---map").forEach(s -> System.out.println(s));AI写代码java运行
在filter的基础上,给每个元素后面添加字符串”—map”
flatMap:如果流的元素为数组或者Collection,flatMap就是将每个Object[]元素或Collection元素都转换为Object元素。
List<String[]> setList = new ArrayList<>();setList.add(new String[]{"aa","bb"});setList.add(new String[]{"cc","dd"});setList.add(new String[]{"ee","ff"});//使用map方法setList.stream().map(s->Arrays.stream(s)).forEach(s-> System.out.println("map==" + s));//使用flatMap方法setList.stream().flatMap(s->Arrays.stream(s)).forEach(s-> System.out.println("flatMap==" + s));AI写代码java运行
map就是将数组流直接返回,flatMap是将数组流中的每个元素都返回。
.distinct:将集合中的元素去重。
List<String> disList = Arrays.asList("aaa","ddd","bbb","ddd","aaa");disList.stream().distinct().forEach(s-> System.out.println(s));AI写代码java运行
sorted:将集合中的元素排序。
List<Integer> integerList = Arrays.asList(2,4,1,3);integerList.stream().sorted().forEach(s-> System.out.println(s));AI写代码java运行
可以按照自定义排序:
List<Integer> integerList = Arrays.asList(2,4,1,3);integerList.stream().sorted((s1,s2)->s2.compareTo(s1)).forEach(s-> System.out.println(s));AI写代码java运行
peek:生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数即引用的方法A,当Stream每个元素被消费的时候都会先
执行新Stream给定的方法A。peek是中间操作,如果peek后没有最终操作,则peek不会执行。
List<Integer> integerList = Arrays.asList(1,2,3,4);integerList.stream().peek(s-> System.out.println("peek = "+s)).forEach(s-> System.out.println("forEach = "+s));AI写代码java运行
limit:返回Stream的前n个元素。
List<Integer> integerList = Arrays.asList(1,2,3,4);integerList.stream().limit(2).forEach(s-> System.out.println(s));AI写代码java运行
skip:删除Stream的前n个元素。
List<Integer> integerList = Arrays.asList(1,2,3,4);integerList.stream().skip(2).forEach(s-> System.out.println(s));AI写代码java运行
终端操作(terminal)
1.forEach:遍历Stream中的每个元素,前面每个例子都有使用,此处不再演示。
List<Integer> integerList = Arrays.asList(1,2,3,4);integerList.stream().skip(2).forEach(s-> System.out.println(s));AI写代码java运行
forEachOrdered:遍历Stream中的每个元素。
区别: 在串行流(stream)中没有区别,在并行流(parallelStream)中如果数据源是有序集合,forEachOrdered输出顺序与数据源中顺序
一致,forEach则是乱序。
List<Integer> integerList = Arrays.asList(1,2,3,4);integerList.parallelStream().forEachOrdered(s-> System.out.println(s));AI写代码java运行
toArray:将流转换为Object[]或者指定类型的数组。
List<Integer> integerList = Arrays.asList(1,2,3,4);Object[] array = integerList.stream().toArray();String[] strArr = integerList.stream().toArray(String[]::new);AI写代码java运行
Stream中的toArray普通情况下和集合中的toArray没什么区别,但是Stream中的toArray转换为指定类型的数组。
reduce:将集合中的每个元素聚合成一条数据。有三种情况:
reduce(BinaryOperator accumulator):此处需要一个参数,返回Optional对象:
Optional reduce = integerList.stream().reduce((a, b) -> a + b);
reduce(T identity, BinaryOperator accumulator):此处需要两个参数,第一个参数为起始值,第二个参数为引用的方法
从起始值开始,每个元素执行一次引用的方法(方法引用的中的两个参数:第一个参数为上个元素执行方法引用的结果,第二个参数为当前元素)。
List<Integer> integerList = Arrays.asList(1,2,3,4);int integer = integerList.stream().reduce(5,(a, b) -> a + b);System.out.println(integer);AI写代码java运行
此例中使用起始值为5,对集合中每个元素求和,可以理解为:5+1+2+3+4=15。
**reduce:**此处需要三个参数。此方法用在并发流(parallelStream)中,启动多个子线程使用accumulator进行并行计算,最终使用combiner对子线程结果进行合并,返回identity类型的数据。
collect:将流转换成集合或聚合元素。有两种情况。接受一个参数和接受三个参数(三个参数在并发流parallelStream中使用),此处介绍一个参数的情况,单个参数接受的参数类型为Collector,Collectors 类实现了很多归约操作
List<Integer> integerList = Arrays.asList(2,4,1,3);List<Integer> integers = integerList.stream().filter(s -> s > 1).collect(Collectors.toList());System.out.println(integers.toString());AI写代码java运行
此处统计集合中大于1的元素并最终返回list。
min:获取集合中最小值。
List<Integer> integerList = Arrays.asList(2,4,1,3);Integer min = integerList.stream().min(Integer::compareTo).get();System.out.println(min);AI写代码java运行
max:获取集合中最大值。
List<Integer> integerList = Arrays.asList(2,4,1,3);Integer max = integerList.stream().max(Integer::compareTo).get();System.out.println(max);AI写代码java运行
count:获取集合中元素个数
List<Integer> integerList = Arrays.asList(2,4,1,3);long count = integerList.stream().count();System.out.println(count);AI写代码java运行
原文地址:https://blog.csdn.net/m0_57376564/article/details/148143797