狂命爆肝21天,共51K字的JAVA学习笔记奉上,JAVA从入门到精通一文搞定,一文在手JAVA无忧
背景知识
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语句;}}总结
- JavaSE 是基础版,JavaEE 是企业版,JavaME 是微型版。
- 编译 使用
javac,运行 使用java。 - JDK 是开发工具包,JRE 是运行环境,JVM 是虚拟机。
- UML 用于面向对象设计,注释 用于代码说明。
- 类与方法 的基本结构如上所示。
一、集成开发环境(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)。
方法(函数)
- 调用:
类名.方法名(实参列表); - 实例方法: 不带
static,需要对象参与。 - 静态方法: 带
static,与对象无关。
定义:
[修饰符列表] 返回值类型 方法名(形参列表){ 方法体;return;// return 后不能跟语句}方法重载(Overload)
- 定义: 在同一类中,方法名相同但参数列表不同。
- 特点: 与返回值类型和修饰符列表无关。
方法递归
- 定义: 方法调用自身,每次递归都会分配新的内存空间(压栈)。
示例:
publicstaticintsum(int n){if(n ==1){return1;}return n +sum(n -1);}方法覆盖(Override)
- 定义: 发生在继承关系中,子类重写父类的方法。
- 规则:
- 方法名、返回值类型、形参列表必须与父类一致。
- 访问权限不能比父类更低,抛出异常不能更多。
- 限制:
- 私有方法、构造方法不能覆盖。
- 静态方法不存在覆盖。
总结
- 标识符: 命名规则与用途。
- 变量: 局部变量、实例变量、静态变量的定义与存储位置。
- 数据类型: 基本数据类型与引用数据类型的区别。
- 位运算符: 各种位运算符的作用与用法。
- 方法: 定义、调用、重载、递归与覆盖的规则与特点。
五、Java 控制流与 Lambda 表达式
1. 控制流语句
Do-While 循环:
do{// 循环体}while(布尔表达式);While 循环:
while(表达式){// 循环体}增强 For 循环(For Each):
for(元素类型 变量名 : 数组或集合){System.out.println(变量名);}For 循环:
for(初始表达式; 布尔表达式; 更新循环体){// 循环体}Switch 语句:
switch(关键词){case 关键词:// java语句break;default:// 默认语句}If-Else 语句:
if(条件){// 语句}elseif(表达式){// 语句}else{// 语句}2. Java 标签
- 标签用于控制嵌套循环的跳转和中断。
- 语法:
label: - 用法:
continue label;:跳过当前循环,继续执行标签处的循环。break label;:结束标签处的循环,执行循环后的代码。
3. Lambda 表达式
计算集合元素的最大值、最小值、总和以及平均值:
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());对列表的每个元素应用函数:
List<String>G7=Arrays.asList("USA","Japan","France","Germany","Italy","U.K.","Canada");StringG7Countries=G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));System.out.println(G7Countries);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);列表迭代:
// 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);事件处理:
// Java 8 之前JButton show =newJButton("Show"); show.addActionListener(newActionListener(){@OverridepublicvoidactionPerformed(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");});实现 Runnable:
// Java 8 之前newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("Before Java8, too much code for too little to do");}}).start();// Java 8 方式newThread(()->System.out.println("In Java8, Lambda expression rocks !!")).start();总结
- 控制流语句:用于控制程序的执行流程,包括条件判断、循环等。
- Java 标签:用于控制嵌套循环的跳转和中断。
- Lambda 表达式:简化了匿名类的使用,使代码更简洁,特别是在实现函数式接口(如
Runnable、ActionListener)时非常有用。 - Stream API:提供了强大的集合操作功能,如
map、reduce、forEach等,使得对集合的处理更加高效和简洁。
六、面向对象
面向过程与面向对象的对比
- 面向过程:
- 因果关系:关注问题的具体步骤和流程。
- 具体过程:强调如何一步步解决问题。
- 耦合度高:各个模块之间依赖性强,修改一个模块可能会影响其他模块。
- 软件拓展性差:由于耦合度高,系统的扩展和维护较为困难。
- 面向对象:
- 分类对象:将问题分解为多个对象,每个对象负责特定的功能。
- 关系层度低:对象之间的依赖关系较弱,耦合度低。
- 关注对象功能:关注对象能完成哪些功能,而不是具体的实现步骤。
- 三大特征:
- 封装性:将复杂的事务封装起来,只保留简单的操作入口。封装后形成独立的对象,提高了代码的复用性、适应性和安全性。
- 继承性:实现代码复用,最重要的是支持多态和方法覆盖。
- 多态性:父类型的引用可以指向子类型对象,降低程序耦合度,提高扩展力。
面向对象的分析与设计
- 面向对象的分析(OOA):分析问题域,识别对象及其关系。
- 面向对象的设计(OOD):设计对象的结构和行为,定义类及其关系。
- 面向对象的编程(OOP):使用编程语言实现设计,创建对象并实现其功能。
类与对象
- 类:高度抽象的对象的集合,是一个模板。
- 静态代码块:类加载时执行。
- 实例代码块:实例化时执行。
- 静态变量:类级别的变量。
- 实例变量:对象级别的变量,存储在堆内存中。
- 构造方法:创建对象时调用,用于初始化实例变量。
- 静态方法:类级别的方法。
- 实例方法:对象级别的方法。
- 成员变量:对象的属性,描述对象的状态。
- 成员方法:对象的行为,描述对象的动作。
- 对象:类的具体实例。
- 创建对象:
类名 对象名称 = new 类名(); - 使用对象:
对象名称.属性名或对象名称.方法名() - 修改对象:
引用.变量名 = 值 - 引用与对象:引用保存了对象的地址,指向堆内存中的对象。多个引用可以指向同一个对象,但一个引用只能指向一个对象。
- 创建对象:
User u=newUser();Address a=newAddress(); u.addr=a; Print(u.addr.city);A.city=”天津”; Print(u.addr.city);封装
- 私有化属性:使用
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关键字修饰的类,是类的进一步抽象。 - 属于引用数据类型。
- 使用
- 特点:
- 不能使用
private或final修饰。 - 抽象类可以包含抽象方法和非抽象方法。
- 抽象类的子类可以是抽象类或非抽象类。
- 不能实例化(不能创建对象),但可以有构造方法,供子类使用。
- 不能使用
- 抽象方法:
- 使用
abstract关键字修饰,无方法体。 - 语法:
[修饰符列表] abstract 返回值类型 方法名(); - 包含抽象方法的类一定是抽象类。
- 使用
- 规则:
- 抽象类不一定有抽象方法,但抽象方法必须出现在抽象类中。
- 非抽象类继承抽象类时,必须实现所有抽象方法。
语法:
[修饰符列表]abstractclass 类名 {}接口
- 定义:
- 使用
interface关键字定义,是完全抽象的(特殊的抽象类)。 - 属于引用数据类型。
- 使用
- 特点:
- 接口中只能包含常量和抽象方法(默认
public static final和public abstract,修饰符可省略)。 - 支持多继承,一个接口可以继承多个接口。
- 接口不能继承抽象类。
- 接口中只能包含常量和抽象方法(默认
- 方法类型:
- 抽象方法:
abstract修饰(可省略)。 - 默认方法:
default修饰,提供默认实现。 - 静态方法:
static修饰,通过接口名调用。
- 抽象方法:
- 实现:
- 类通过
implements关键字实现接口。 - 非抽象类实现接口时,必须重写所有抽象方法。
- 一个类可以实现多个接口。
- 类通过
- 多态:
- 接口支持多态:
父类型引用指向子类对象。 - 示例:
接口名 引用 = new 实现类();
- 接口支持多态:
- 作用:
- 解耦合:调用者面向接口调用,实现者面向接口编写实现。
- 扩展性强:接口+多态可以降低程序耦合度。
语法:
[修饰符列表]interface 接口名 {}抽象类与接口的区别
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 抽象程度 | 半抽象(可以包含具体方法) | 完全抽象(只能包含抽象方法) |
| 构造方法 | 有构造方法,供子类使用 | 无构造方法 |
| 继承 | 单继承(一个类只能继承一个抽象类) | 支持多继承(一个类可以实现多个接口) |
| 内容 | 可以包含抽象方法和非抽象方法 | 只能包含常量和抽象方法 |
| 用途 | 抽象行为和数据 | 主要抽象行为 |
| 实例化 | 不能实例化 | 不能实例化 |
开发中的选择
- 抽象类:
- 当多个类有共同的属性和行为,且需要部分具体实现时使用。
- 适合定义“是什么”(
is-a关系)。
- 接口:
- 当需要定义一组行为规范,且不关心具体实现时使用。
- 适合定义“能做什么”(
like-a关系)。
示例
实现与继承:
classBirdextendsAnimalimplementsFlyable{@Overridevoidsound(){System.out.println("Chirp...");}@Overridepublicvoidfly(){System.out.println("Flying...");}}接口:
interfaceFlyable{voidfly();}抽象类:
abstractclassAnimal{abstractvoidsound();voidsleep(){System.out.println("Sleeping...");}}总结
- 面向对象编程通过封装、继承和多态三大特征,提高了代码的复用性、扩展性和维护性。
- 类与对象是面向对象编程的基础,类是对对象的抽象,对象是类的实例。
- 封装通过私有化属性和提供操作入口,增强了代码的安全性和可控性。
- 继承实现了代码的复用,并支持多态和方法覆盖。
- 多态通过向上转型和向下转型,降低了程序的耦合度,提高了扩展力。
- 面向抽象编程,而不是面向具体,可以进一步降低耦合度,提高系统的灵活性和可扩展性。
- 抽象类 用于定义类的共有特征,支持部分具体实现。
- 接口 用于定义行为规范,支持多继承和解耦合。
- 在实际开发中,根据需求选择抽象类或接口,合理使用可以提高代码的扩展性和可维护性。
七、类库
源码、字节码与帮助文档
- 源码:
- 理解程序:源码是程序员编写的原始代码,用于理解程序的逻辑和功能。
- 字节码:
- 程序开发使用:字节码是源码编译后的中间代码,由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)
枚举是一种特殊的类,用于定义一组常量:
enumSeason{SPRING,SUMMER,AUTUMN,WINTER}内部类
- 成员内部类:
- 定义在类中,可以访问外部类的所有成员。
- 局部内部类:
- 定义在方法中,只能在该方法内访问。
- 静态内部类:
- 使用
static修饰,只能访问外部类的静态成员。
- 使用
- 匿名内部类:
- 没有名称的内部类,通常用于实现接口或抽象类。
总结
- 源码是理解程序的基础,字节码是程序运行的关键,帮助文档是开发的指南。
- Object是Java的根类,提供了对象的基本操作。
- System类提供了系统级别的操作,如垃圾回收、时间获取等。
- String类用于操作字符串,提供了丰富的构造方法和方法。
- StringBuffer和StringBuilder用于字符串的拼接和修改,前者线程安全,后者性能更优。
- 包装类用于将基本数据类型转换为对象。
- 日期相关类用于处理日期和时间。
- 内部类提供了更灵活的代码组织方式。
八、数组
一维数组
- 定义:
- 数组是引用数据类型,存储在堆内存中。
- 可以存储各种数据类型,但不能直接存储对象,存储的是对象的引用(内存地址)。
- 特点:
- 数组元素类型统一,最后一个下标为
length - 1。 - 带有
length属性,用于获取数组长度。
- 数组元素类型统一,最后一个下标为
- 优点:
- 查询、查找、检索某个下标元素效率极高(内存连续,类型相同)。
- 缺点:
- 随机增删元素效率较低。
- 不能存储大数据量。
- 定义与初始化:
- 遍历:
- 方法参数:
- main 方法的数组参数:
- 存储对象:
- 数组扩容:
- 数组拷贝:
使用 System.arraycopy 方法:
System.arraycopy(原数组, 原起点, 目标数组, 目标下标, 长度);新建一个大数组,然后将原数组拷贝过去:
int[] newArray =newint[原数组.length *2];System.arraycopy(原数组,0, newArray,0, 原数组.length);数组可以存储对象的引用:
类名[] 数组名 =new 类名[长度]; 数组名[0]=new 类名();main 方法的参数是一个字符串数组,用于接收命令行参数:
publicstaticvoidmain(String[] args){}数组可以作为方法的参数:
void 方法名(数据类型[] 数组名){}使用 for 循环或增强 for 循环:
for(int i =0; i < 数组名.length; i++){System.out.println(数组名[i]);}赋值:
数组名[下标]= 值;动态初始化:
数据类型[] 数组名 =new 数据类型[长度];静态初始化:
数据类型[] 数组名 ={元素1, 元素2,...};二维数组
- 定义:
- 二维数组是数组的数组,可以看作是一个表格。
- 初始化:
- 遍历:
使用嵌套 for 循环:
for(int i =0; i < 数组名.length; i++){for(int j =0; j < 数组名[i].length; j++){System.out.println(数组名[i][j]);}}动态初始化:
数据类型[][] 数组名 =new 数据类型[行数][列数];静态初始化:
数据类型[][] 数组名 ={{元素1, 元素2},{元素3, 元素4}};语法:
数据类型[][] 数组名 =new 数据类型[行数][列数];总结
- 一维数组:
- 适用于存储一组相同类型的数据。
- 查询效率高,增删效率低。
- 可以通过
length属性获取长度。 - 支持静态初始化和动态初始化。
- 二维数组:
- 适用于存储表格型数据。
- 可以看作是一维数组的数组。
- 支持静态初始化和动态初始化。
- 数组的优缺点:
- 优点:查询效率高,内存连续。
- 缺点:增删效率低,不能存储大数据量。
- 数组的应用场景:
- 存储一组固定长度的数据。
- 存储对象引用。
- 存储表格型数据(二维数组)。
示例
数组扩容:
int[] src ={1,2,3};int[] dest =newint[src.length *2];System.arraycopy(src,0, dest,0, src.length);数组存储对象:
Animal[] animals =newAnimal[2]; animals[0]=newCat(); animals[1]=newDog();二维数组:
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]);}}一维数组:
int[] arr ={1,2,3,4,5};for(int i =0; i < arr.length; i++){System.out.println(arr[i]);}通过合理使用数组,可以高效地存储和操作数据,但需要注意其增删效率较低的缺点。
九、算法
以下是常见 排序算法 和 查找算法 的思想总结,并附带 Java 实例:
排序算法
- 冒泡排序(Bubble Sort):
- 思想:重复遍历数组,每次比较相邻元素,如果顺序错误则交换,直到没有需要交换的元素。
- 时间复杂度:O(n²)。
- 选择排序(Selection Sort):
- 思想:每次从未排序部分选择最小元素,放到已排序部分的末尾。
- 时间复杂度:O(n²)。
- 插入排序(Insertion Sort):
- 思想:将未排序部分的元素逐个插入到已排序部分的正确位置。
- 时间复杂度:O(n²)。
- 快速排序(Quick Sort):
- 思想:选择一个基准元素,将数组分为两部分,左边小于基准,右边大于基准,递归排序。
- 时间复杂度:O(n log n)。
- 归并排序(Merge Sort):
- 思想:将数组分成两半,分别排序,然后合并。
- 时间复杂度:O(n log n)。
Java 实现:
publicstaticvoidmergeSort(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);}}privatestaticvoidmerge(int[] arr,int left,int mid,int right){int[] temp =newint[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];}}Java 实现:
publicstaticvoidquickSort(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);}}privatestaticintpartition(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;}Java 实现:
publicstaticvoidinsertionSort(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;}}Java 实现:
publicstaticvoidselectionSort(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;}}Java 实现:
publicstaticvoidbubbleSort(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;}}}}查找算法
- 线性查找(Linear Search):
- 思想:从头到尾遍历数组,逐个比较,找到目标元素。
- 时间复杂度:O(n)。
- 二分查找(Binary Search):
- 思想:在有序数组中,每次取中间元素与目标比较,缩小查找范围。
- 时间复杂度:O(log n)。
Java 实现:
publicstaticintbinarySearch(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;}elseif(arr[mid]< target){ left = mid +1;}else{ right = mid -1;}}return-1;}Java 实现:
publicstaticintlinearSearch(int[] arr,int target){for(int i =0; i < arr.length; i++){if(arr[i]== target){return i;}}return-1;}总结
- 排序算法:
- 冒泡排序:简单但效率低,适合小规模数据。
- 选择排序:每次选择最小元素,适合小规模数据。
- 插入排序:适合部分有序的数据。
- 快速排序:高效,适合大规模数据。
- 归并排序:稳定且高效,适合大规模数据。
- 查找算法:
- 线性查找:适合无序数据。
- 二分查找:适合有序数据,效率高。
- 选择依据:
- 数据规模、是否有序、稳定性要求等。
示例
publicclassMain{publicstaticvoidmain(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 :"未找到"));}}输出:
冒泡排序结果: [2, 3, 4, 5, 8] 快速排序结果: [2, 3, 4, 5, 8] 二分查找结果: 找到,下标为 2 通过合理选择排序和查找算法,可以高效地处理数据。
十、异常
1. 异常的基本概念
- 异常在 Java 中以类的方式存在,每个异常类都可以创建异常对象。
- 方法覆盖规则:子类重写父类方法时,不能抛出比父类方法更高的异常(运行时异常
RuntimeException除外)。 - 异常的分类:
java.lang.Throwable:异常的父类,有两个子类:Error:错误,通常是系统级错误(如OutOfMemoryError),不可处理,只能退出程序。Exception:异常,所有异常都是在运行阶段发生的。Exception的直接子类:编译时异常(受检异常CheckedException),需要在编写程序时预处理。RuntimeException:运行时异常,通常由程序逻辑错误引起,不需要显式处理。
2. 常见运行时异常
NullPointerException:空指针异常,尝试访问null对象的成员。ArrayIndexOutOfBoundsException:数组下标越界异常。ClassCastException:类型转换异常,尝试将对象强制转换为不兼容的类型。NumberFormatException:数字转换异常,尝试将非数字字符串转换为数字。
3. 异常处理方式
throws关键字:- 在方法声明位置使用,将异常抛给调用者处理。
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 块执行");}示例:
publicvoidreadFile()throwsIOException{// 可能抛出 IOException 的代码}4. 常用异常方法
getMessage():获取异常的简单描述信息(通常是构造方法的参数)。printStackTrace():打印异常的堆栈追踪信息(异步线程中常用)。
5. 自定义异常
- 步骤:
- 编写一个类继承
Exception(受检异常)或RuntimeException(运行时异常)。 - 提供两个构造方法:一个无参,一个有参。
- 使用
throw手动抛出异常。
- 编写一个类继承
示例:
// 自定义异常类publicclassMyExceptionextendsException{publicMyException(){super();}publicMyException(String message){super(message);}}// 使用自定义异常publicclassTest{publicstaticvoidmain(String[] args){try{thrownewMyException("自定义异常发生");}catch(MyException e){System.out.println(e.getMessage());}}}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 =newFile("test.txt");if(!file.exists()){ file.createNewFile();// 创建文件}System.out.println("文件名称: "+ file.getName());System.out.println("文件路径: "+ file.getAbsolutePath()); file.delete();// 删除文件字节流读写
// 写入文件try(FileOutputStream fos =newFileOutputStream("output.txt")){ fos.write("Hello, World!".getBytes()); fos.flush();}// 读取文件try(FileInputStream fis =newFileInputStream("output.txt")){byte[] buffer =newbyte[1024];int len;while((len = fis.read(buffer))!=-1){System.out.println(newString(buffer,0, len));}}通过掌握这些核心概念和类库,可以高效地处理文件操作和字节流读写。
字符流总结
字符流是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 =newFileWriter("output.txt");BufferedWriter bw =newBufferedWriter(fw)){ bw.write("Hello, World!"); bw.newLine();// 写入换行符 bw.write("This is a test.");}// 读取文件try(FileReader fr =newFileReader("output.txt");BufferedReader br =newBufferedReader(fr)){String line;while((line = br.readLine())!=null){System.out.println(line);}}使用指定编码读写文件
// 写入文件(指定编码为UTF-8)try(OutputStreamWriter osw =newOutputStreamWriter(newFileOutputStream("output.txt"),"UTF-8");BufferedWriter bw =newBufferedWriter(osw)){ bw.write("你好,世界!");}// 读取文件(指定编码为UTF-8)try(InputStreamReader isr =newInputStreamReader(newFileInputStream("output.txt"),"UTF-8");BufferedReader br =newBufferedReader(isr)){String line;while((line = br.readLine())!=null){System.out.println(line);}}总结
- 字符流适用场景:
- 处理文本文件、字符串等字符数据。
- 核心类:
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 部分。
InterfaceCollection
没有使用泛型前可以存储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
- Objectnext() 返回迭代中的下一个元素。(返回object)
- default void remove() 从基础集合中删除此迭代器返回的最后一个元素(可选操作)。
Interface List有序可重复,Collection子接口
- void add(int index, E element) 在此列表中的指定位置插入指定的元素
- Eget(int index) 返回此列表中指定位置处的元素
- Eset(int index, E element) 将此列表中指定位置的元素替换为指定的元素
- int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含该元素,则返回 -1
- int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含该元素,则返回 -1。
- Eremove(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 implementsComparable<user>{//自定义类需要实现接口int age;publicuser(int age){this.age = age;}@OverridepublicStringtoString(){return"user{"+"age="+ age +'}';}@Override//重写比较规则publicintcompareTo(user o){returnthis.age-o.age;//返回==0,value覆盖,返回大于0 到右子树,返回小于0到左子树}}Interface Map<K,V>Map主接口(和Collection没有继承关系)
以Key和Value存储数据都是引用数据类型,都存储内存地址,Key是主导
- Vput(K key, V value) 添加键值对(Key元素需要重新hashCode和equals方法)(Key可以为空,只有一个)
- void clear() 清空Map集合
- Vget(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集合)
- Vremove(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);//组合遍历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
- ObjectsetProperty(String key, String value) 存
- StringgetProperty(String key) 取
- StringgetProperty(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 =newArrayList<String>();2. 泛型的优点
- 类型安全:编译时检查类型,避免运行时类型转换错误。
- 代码复用:可以编写通用的类和方法,适用于多种类型。
- 代码简洁:减少强制类型转换的代码。
3. 泛型的缺点
- 导致集合存储缺少多样性:泛型限制了集合中元素的类型,无法存储多种类型的对象。
- 类型擦除:泛型信息在编译后会被擦除,运行时无法获取泛型的具体类型。
4. 自动推断机制(钻石表达式)
- 引入时间:JDK 7 新特性。
- 作用:自动推断泛型类型,简化代码。
- 语法:只写前面的泛型类型,后面的泛型类型可以省略。
示例:
List<String> list =newArrayList<>();5. 自定义泛型
- 泛型类:
- 在定义类时添加
<T>,T是类型参数。
- 在定义类时添加
- 泛型方法:
- 在定义方法时添加
<T>,T是类型参数。
- 在定义方法时添加
使用:
Integer[] intArray ={1,2,3};printArray(intArray);示例:
public<T>voidprintArray(T[] array){for(T element : array){System.out.println(element);}}使用:
Box<String> box =newBox<>(); box.setValue("Hello");String value = box.getValue();示例:
publicclassBox<T>{privateT value;publicvoidsetValue(T value){this.value = value;}publicTgetValue(){return value;}}6. 泛型的通配符
<?>:表示任意类型。<? extends T>:表示T或其子类型(上界通配符)。<? super T>:表示T或其父类型(下界通配符)。
示例:
publicvoidprintList(List<?> list){for(Object element : list){System.out.println(element);}}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()方法,定义线程执行的任务。
- 编写一个类,直接继承
- 特点:
- 简单易用,但 Java 是单继承,继承
Thread类后无法继承其他类。
- 简单易用,但 Java 是单继承,继承
启动线程:
thread.start();创建线程对象:
MyThread thread =newMyThread();2. 实现 Runnable 接口
- 实现方式:
- 编写一个类,实现
java.lang.Runnable接口。 - 实现
run()方法,定义线程执行的任务。 - 通常使用匿名内部类创建。
- 编写一个类,实现
- 特点:
- 更灵活,可以避免单继承的限制。
- 适合多个线程共享同一个任务。
启动线程:
thread.start();创建线程对象:
Runnable task =newMyRunnable();Thread thread =newThread(task);3. 使用 Callable 和 Future 接口
- 实现方式:
- 编写一个类,实现
java.util.concurrent.Callable接口。 - 实现
call()方法,定义线程执行的任务,并返回结果。
- 编写一个类,实现
- 创建线程对象:
- 特点:
call()方法可以有返回值和抛出异常。- 适合需要获取线程执行结果的场景。
获取结果:
Integer result = futureTask.get();// 阻塞直到获取结果启动线程:
thread.start();使用 FutureTask 对象作为 Thread 的 target 创建线程:
Thread thread =newThread(futureTask);使用 FutureTask 包装 Callable 对象:
FutureTask<Integer> futureTask =newFutureTask<>(task);创建 Callable 实现类的实例:
Callable<Integer> task =newMyCallable();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 中的死锁示例
以下是一个经典的死锁代码示例,展示了两个线程互相等待对方释放锁的情况:
publicclassDeadlockExample{privatestaticfinalObject lock1 =newObject();privatestaticfinalObject lock2 =newObject();publicstaticvoidmain(String[] args){Thread thread1 =newThread(()->{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 =newThread(()->{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();}}代码分析
- 线程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。 - 守护线程中创建的子线程默认也是守护线程。
代码示例
以下是一个守护线程的示例,展示了如何设置守护线程以及它的行为:
publicclassDaemonThreadExample{publicstaticvoidmain(String[] args){Thread daemonThread =newThread(()->{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("主线程执行完毕,程序退出。");}}代码分析
- 守护线程:
- 守护线程是一个死循环,每隔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.forName("包名.类名"):- 加载类并默认进行静态初始化。
- 返回该类的
Class对象。
Class.forName("包名.类名", false, 类加载器):- 第二个参数为
false时,不进行初始化;为true时,进行初始化。
- 第二个参数为
实例对象.getClass():- 对类进行静态初始化和非静态初始化。
- 返回运行时实际对象所属类的
Class对象。
示例:
String str ="Hello";Class<?> clazz = str.getClass();示例:
Class<?> clazz =Class.forName("java.lang.String",false,ClassLoader.getSystemClassLoader());示例:
Class<?> clazz =Class.forName("java.lang.String");示例:
Class<?> clazz =String.class;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对象转换为指定类的子类类型。- 如果
xxx.xxx.xxx是List的子类,则正常执行;否则抛出ClassCastException。
- 如果
动态加载时的应用:
Class.forName("xxx.xxx.xxx").asSubclass(List.class).newInstance();示例:
List<String> strList =newArrayList<>();Class<?extendsList> strListCast = strList.getClass().asSubclass(List.class);6. 静态加载与动态加载
- 静态加载:通过
new ClassName()加载类,编译时必须提供类的定义。 - 动态加载:通过
Class.forName("ClassName")加载类,编译时可以缺席,运行时按需提供。
总结
- Class 对象:存储类的所有信息,是反射机制的核心。
- 生成方式:
类名.class、Class.forName()、实例对象.getClass()。 - 常用方法:
getName()、getSuperclass()、getInterfaces()、asSubclass()等。 asSubclass:用于将Class对象转换为指定类的子类类型。- 静态加载与动态加载:静态加载在编译时提供类定义,动态加载在运行时按需提供。
通过掌握 Class 对象和反射机制,可以在运行时动态操作类的成员,实现灵活的编程。
十六、小游戏(进击的小鸟)
publicclassStartGame{//游戏开始类publicstaticvoidmain(String[] args)throwsInterruptedException{JFrame jFrame =newJFrame("进击の小鸟");//创建窗口对象 jFrame.setSize(400,600);//窗口大小 jFrame.setLocationRelativeTo(null);//窗口相对位置 jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设定点击关闭结束程序 BirdGame birdGame =newBirdGame();//初始化游戏对象类 jFrame.add(birdGame);//把创建好的对象加进来 jFrame.setVisible(true);//让窗口可视化 birdGame.action();//地面运动方法}}publicclassBird{publicBufferedImage images[];publicBufferedImage image;//存放小鸟图片publicint x;publicint y;publicint width;publicint height;publicint index=0;publicdouble speed=0;//小鸟初始速度publicdouble upspeed=30;//初始上抛速度publicdouble s=0;//经过t,发生的位移publicdouble t=0.2;//发生位移时间publicdouble g=9.8;//重力加速度publicBird()throwsIOException{ x=120; y=120; images=newBufferedImage[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"));}}publicvoidfly(){//小鸟飞飞 index++; image=images[index/2%8];}publicvoidupSpeed(){//鼠标点击游戏屏幕,给小鸟一个初始上抛速度 speed=upspeed;}publicvoiddistanceChange(){//实现小鸟速度,位移,纵坐标变化double v=speed;//初始速度 s=v*t-g*t*t/2;//经过t小鸟的位移 speed=v-g*t;//小鸟经过时间t的末速度 y=y-(int)s;//经过时间t后,小鸟的y}}publicclassColumn{//管道类publicBufferedImage cImage;publicint x;publicint y;publicint width;publicint height;publicint distance=270;//两根管道之间的距离publicstaticint count=0;Random random =newRandom();publicColumn()throwsIOException{ 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++;}publicvoidstep(){ x-=5;//让地面往左运动if(x<=-width/2){ x=x+distance*2; y=-(height/2-random.nextInt(300)-50);//x=400;}}}publicclassGround{//地面类publicBufferedImage image;//存放地面图片publicint x;publicint y;publicGround(){ try{ x=0; y=500; image=ImageIO.read(getClass().getResource("ground.png")); }catch(IOException e){ e.printStackTrace(); }}publicvoidstep(){ x-=1;//让地面往左运动 if(x==-100){ x=0; }}}publicclassMusicimplementsRunnable{//音乐类Player player=null;@Overridepublicvoidrun(){InputStream resourceAsStream =this.getClass().getResourceAsStream("2.mp3");try{ player=newPlayer(resourceAsStream); player.play();}catch(JavaLayerException e){ e.printStackTrace();}}publicvoidstopBGM(){if(player!=null) player.close();}}publicclassScore{//连接对象privateString sid;privateint score;privateString time;publicStringgetSid(){return sid;}publicvoidsetSid(String sid){this.sid = sid;}publicintgetScore(){return score;}publicvoidsetScore(int score){this.score = score;}publicStringgetTime(){return time;}publicvoidsetTime(String time){this.time = time;}}publicclassScoreManager{//jdbc连接static{try{Class.forName("com.mysql.cj.jdbc.Driver");}catch(ClassNotFoundException e){ e.printStackTrace();}}publicList<Score>selectAllScore(){//查询方法List<Score> list =newArrayList<>(); 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 =newScore(); 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;}publicintinsertScore(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 =newDate(); SimpleDateFormat simpleDateFormat =newSimpleDateFormat("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;}}publicclassBirdGameextendsJPanel{//自定义面板类继承面板类ScoreManager sc=newScoreManager();publicJPanel jp=newJPanel();publicBufferedImage bg;//图片缓冲区(在显示图片前对图片进行操作 eg:.getWidth()宽,.getHeight()高)publicBufferedImage startbg;publicBufferedImage overbg;publicGround ground;publicBird bird;publicColumn columns[];publicMusic music;String file="H:\\Java程序\\小程序\\src\\小鸟\\png\\bg.png";publicint state;//表示游戏状态publicstaticfinalintSTART=0;//开始publicstaticfinalintRUNNING=1;//运行publicstaticfinalintGAMEOVER=2;//结束publicstaticint score=0;//初始积分publicBirdGame(){try{ state=START;//游戏初始为游戏开始状态 ground=newGround();//创建地面类对象,调用地面类构造方法 bird =newBird(); columns=newColumn[2]; music =newMusic();for(int i=0;i<columns.length;i++){ columns[i]=newColumn();} 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();}}@Overridepublicvoidpaint(Graphics g){//绘制一次的画画方法super.paint(g);//调用画笔 g.drawImage(bg,0,0,null);//绘制背景(最后一个参数为观察者switch(state){caseSTART://绘制游戏开始图片settishi(g); g.drawImage(startbg,0,0,null); break; caseRUNNING: for(int i=0;i<columns.length;i++){ g.drawImage(columns[i].cImage, columns[i].x, columns[i].y,null); } break; caseGAMEOVER: //绘制游戏结束图片 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);}publicbooleanisHitGround(){//撞击地面 if(bird.y+bird.height>500){ returntrue; }else{ returnfalse; }}publicbooleanisHitSky(){//撞击天空 if(bird.y<0){ returntrue; }else{ returnfalse; }}publicbooleanisguandao(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){ returntrue; }else{ returnfalse; } }else{ returnfalse; }}publicvoidsetScore(Graphics g){//绘制分数方法Font font =newFont(Font.SERIF,Font.ITALIC,40);//罗马字体,斜体,40号 g.setFont(font);//获取字体 g.setColor(Color.white);//获取颜色 g.drawString(score+"分",40,60);//画字符串}publicvoidsettishi(Graphics g){//绘制分数方法Font font1 =newFont(Font.SERIF,Font.BOLD,25);//罗马字体,斜体,40号 g.setFont(font1);//获取字体 g.setColor(Color.black);//获取颜色 g.drawString("点击屏幕开始运行",110,400);//画字符串 g.drawString(" 制作人---赵嘉盟",120,430);}publicvoidsettishi2(Graphics g){//绘制分数方法Font font2 =newFont(Font.SANS_SERIF,Font.BOLD,30);//罗马字体,斜体,40号 g.setFont(font2);//获取字体 g.setColor(Color.red);//获取颜色 g.drawString("点击屏幕重新开始",100,500);//画字符串}publicvoidaction()throwsInterruptedException{//游戏对象运动方法this.addMouseListener(newBirdMouseListener());//添加鼠标监听器 while(true){ switch(state){//状态不同,对象运动效果不同 caseSTART: ground.step();//调用地面运动方法 bird.fly();break;caseRUNNING: 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; caseGAMEOVER: music.stopBGM(); break; } repaint();//刷新方法(重新绘制) Thread.sleep(50);//线程睡眠 }}classBirdMouseListenerextendsMouseAdapter{//小鸟飞行鼠标控制监听内部类 @Override publicvoidmousePressed(MouseEvent e){ super.mousePressed(e); switch(state){ caseSTART: state=RUNNING;//鼠标点击开始运行 Thread thread =newThread(music); thread.start(); break; caseRUNNING: bird.upSpeed();//鼠标点击屏幕给小鸟一个初始上抛速度 break; caseGAMEOVER: 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]=newColumn(); }catch(IOException ex){ ex.printStackTrace(); } try{ columns[1]=newColumn(); }catch(IOException ex){ ex.printStackTrace(); } score =0;//给积分初始化 for(int i=0;i<columns.length;i++){ try{ columns[i]=newColumn(); }catch(IOException ex){ ex.printStackTrace(); } } break; } }}}十七、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));以上代码使用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));在filter的基础上,给每个元素后面添加字符串”—map”
flatMap:如果流的元素为数组或者Collection,flatMap就是将每个Object[]元素或Collection元素都转换为Object元素。
List<String[]> setList =newArrayList<>(); setList.add(newString[]{"aa","bb"}); setList.add(newString[]{"cc","dd"}); setList.add(newString[]{"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));map就是将数组流直接返回,flatMap是将数组流中的每个元素都返回。
.distinct:将集合中的元素去重。
List<String> disList =Arrays.asList("aaa","ddd","bbb","ddd","aaa"); disList.stream().distinct().forEach(s->System.out.println(s));sorted:将集合中的元素排序。
List<Integer> integerList =Arrays.asList(2,4,1,3); integerList.stream().sorted().forEach(s->System.out.println(s));可以按照自定义排序:
List<Integer> integerList =Arrays.asList(2,4,1,3); integerList.stream().sorted((s1,s2)->s2.compareTo(s1)).forEach(s->System.out.println(s));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));limit:返回Stream的前n个元素。
List<Integer> integerList =Arrays.asList(1,2,3,4); integerList.stream().limit(2).forEach(s->System.out.println(s));skip:删除Stream的前n个元素。
List<Integer> integerList =Arrays.asList(1,2,3,4); integerList.stream().skip(2).forEach(s->System.out.println(s));终端操作(terminal)
1.forEach:遍历Stream中的每个元素,前面每个例子都有使用,此处不再演示。
List<Integer> integerList =Arrays.asList(1,2,3,4); integerList.stream().skip(2).forEach(s->System.out.println(s));forEachOrdered:遍历Stream中的每个元素。
区别: 在串行流(stream)中没有区别,在并行流(parallelStream)中如果数据源是有序集合,forEachOrdered输出顺序与数据源中顺序
一致,forEach则是乱序。
List<Integer> integerList =Arrays.asList(1,2,3,4); integerList.parallelStream().forEachOrdered(s->System.out.println(s));toArray:将流转换为Object[]或者指定类型的数组。
List<Integer> integerList =Arrays.asList(1,2,3,4);Object[] array = integerList.stream().toArray();String[] strArr = integerList.stream().toArray(String[]::new);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);此例中使用起始值为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());此处统计集合中大于1的元素并最终返回list。
min:获取集合中最小值。
List<Integer> integerList =Arrays.asList(2,4,1,3);Integer min = integerList.stream().min(Integer::compareTo).get();System.out.println(min);max:获取集合中最大值。
List<Integer> integerList =Arrays.asList(2,4,1,3);Integer max = integerList.stream().max(Integer::compareTo).get();System.out.println(max);count:获取集合中元素个数
List<Integer> integerList =Arrays.asList(2,4,1,3);long count = integerList.stream().count();System.out.println(count);