狂命爆肝21天,共51K字的JAVA学习笔记奉上,JAVA从入门到精通一文搞定,一文在手JAVA无忧

狂命爆肝21天,共51K字的JAVA学习笔记奉上,JAVA从入门到精通一文搞定,一文在手JAVA无忧
在这里插入图片描述

背景知识

Java 相关概念

  1. JavaSE (Java Standard Edition): 基础版,用于开发桌面应用程序。
  2. JavaEE (Java Enterprise Edition): 企业版,用于开发企业级应用程序。
  3. JavaME (Java Micro Edition): 微型版,用于开发嵌入式系统和移动设备应用程序。

编译与运行

  1. 编译阶段:
    • 源文件: .java 文件。
    • 字节码文件: .class 文件。
    • 编译工具: javac.exe,用于将 .java 文件编译为 .class 文件。
      • 命令: javac 文件名.java
      • 编译包: javac -d 编译后存放路径 java源文件路径
  2. 运行阶段:
    • 运行工具: java.exe,用于运行 .class 文件。
      • 命令: java 类名(不带 .class 后缀)
    • JVM (Java Virtual Machine): Java 虚拟机,负责执行字节码文件。

开发环境

  1. JDK (Java Development Kit): Java 开发工具包,包含编译器、调试器等开发工具。
  2. JRE (Java Runtime Environment): Java 运行环境,包含 JVM 和运行 Java 程序所需的库。
  3. JVM (Java Virtual Machine): Java 虚拟机,负责执行字节码文件。

工具与格式

  1. native2ascii: 用于将 Unicode 字符转换为 \u 表示的 ASCII 格式。
  2. UML (Unified Modeling Language): 面向对象设计图,用于表示类、接口、继承、实现等关系。
    • 空心箭头: 指向父类(继承)。
    • 空心虚线箭头: 指向接口(实现)。
    • 实心实线箭头: 表示关联关系。

注释

  1. 单行注释: //
  2. 多行注释: /* */
  3. 文档注释: /** */,用于生成帮助文档。

类与方法结构

类体 { 方法体 { java语句;}}

总结

  • JavaSE 是基础版,JavaEE 是企业版,JavaME 是微型版。
  • 编译 使用 javac运行 使用 java
  • JDK 是开发工具包,JRE 是运行环境,JVM 是虚拟机。
  • UML 用于面向对象设计,注释 用于代码说明。
  • 类与方法 的基本结构如上所示。

Java SE API 和文档

一、集成开发环境(IDEA)

以下是用户提供的快捷键和组织方式的总结:


组织方式

  1. Project(工程): 最高层级,包含多个模块。
  2. Module(模块): 工程下的子模块,包含多个包。
  3. Package(包): 模块下的子包,用于组织类和资源。

字体设置

  • 路径: File -> Settings -> Font
    用于调整编辑器的字体样式和大小。

快捷键分类总结

导航与操作
  1. 展开/移动列表:
    • 左右箭头: 展开或折叠列表。
    • 上下箭头: 在列表中移动。
  2. 切换与定位:
    • Alt+左右箭头: 切换 Java 程序。
    • Alt+上下箭头: 在方法间快速移动。
    • Alt+标号: 打开标号窗口。
    • Ctrl+G: 定位到文件的某一行。
    • Ctrl+点击: 切换源码。
    • Ctrl+H: 查看实现类。
  3. 查找与搜索:
    • Ctrl+Shift+N: 查找文件。
    • Ctrl+N: 查找类文件。
    • Ctrl+F12: 在当前类中查找一个方法。

编辑与格式化
  1. 代码编辑:
    • Ctrl+Y: 删除一行。
    • Shift+F6: 重命名。
    • Alt+拖动: 一次编辑多行。
    • Ctrl+Alt+T: 将选中的代码放在 TRY{}IF{}ELSE{} 中。
  2. 代码提示与自动补全:
    • Ctrl+空格: 代码提示。
    • Ctrl+P: 方法参数提示。
    • Ctrl+J: 自动代码。
    • Ctrl+Alt+Space: 类名或接口名提示。
  3. 格式化与优化:
    • Ctrl+Alt+L: 格式化代码。
    • Ctrl+Alt+I: 自动缩进。
    • Ctrl+Alt+O: 优化导入的类和包。

运行与纠错
  1. 运行程序:
    • Ctrl+Shift+F10: 运行当前程序。
  2. 纠错与提示:
    • Alt+回车: 纠错提示。

窗口操作
  1. 全屏模式:
    • Ctrl+Shift+F12: 切换全屏模式。

总结

  • 组织方式: 工程 -> 模块 -> 包,层级清晰,便于管理。
  • 快捷键:
    • 导航与查找:快速定位文件、类、方法。
    • 编辑与格式化:提高代码编写效率。
    • 运行与纠错:快速运行程序并修复错误。
    • 窗口操作:优化开发环境布局。

二、JVM内存划分

局部变量在方法体中声明,运行阶段内存在栈中分配

方法区内存:字节码文件在加载 的时候将其放在方法区之中(最先有数据,调用方法时在栈内分配空间)
堆内存(heap):new对象(成员变量中的实例变量(一个对象一份)在java对象内部存储),只能通过引用调用操作
栈(stack)内存:栈帧永远指向栈顶元素,栈顶元素处于活跃状态,先进后出,后进先出(存储局部变量)
在这里插入图片描述

内存区域与数据存储

  1. 堆内存(Heap):
    • 存储实例变量(对象属性)。
    • 每个 JVM 实例只有一个堆内存,所有线程共享。
    • 垃圾回收器(GC)主要针对堆内存进行回收。
  2. 方法区(Method Area):
    • 存储静态变量(类变量)和类元数据(如类信息、常量池等)。
    • 每个 JVM 实例只有一个方法区,所有线程共享。
    • 方法区是最先有数据的内存区域,因为类加载时静态变量和类信息会初始化。
  3. 栈内存(Stack):
    • 存储局部变量和方法调用栈帧。
    • 每个线程有一个独立的栈内存,线程私有。
    • 栈内存是使用最频繁的内存区域,因为方法调用和局部变量的生命周期较短。

变量存储位置

  1. 局部变量:
    • 存储在栈内存中。
    • 生命周期与方法调用一致,方法结束时局部变量会被销毁。
  2. 实例变量:
    • 存储在堆内存中。
    • 生命周期与对象一致,对象被垃圾回收时实例变量会被销毁。
  3. 静态变量:
    • 存储在方法区中。
    • 生命周期与类一致,类卸载时静态变量会被销毁。

垃圾回收器(GC)

  1. 主要目标:
    • 垃圾回收器主要针对堆内存进行回收,清理不再使用的对象。
    • 栈内存和方法区的垃圾回收机制与堆内存不同。
  2. 特点:
    • 堆内存是垃圾回收的主要区域,因为对象生命周期较长且占用内存较大。
    • 栈内存和方法区的垃圾回收效率较高,因为它们的生命周期较短且数据量相对较小。

三、关键字:

类与关键字

  1. public:
    • 表示公开的类,类名必须与文件名一致,且一个文件中只能有一个 public 类。
  2. class:
    • 用于定义一个类。
  3. static:
    • 表示静态的,修饰的成员变量或方法属于类级别,不依赖于对象。
    • 静态变量在类加载时初始化,存储在方法区内存中。
    • 静态方法不能访问实例变量或实例方法,需要通过对象访问。
  4. break:
    • 用于跳出循环或 switch 语句。
  5. continue:
    • 用于跳过当前循环的剩余部分,直接进入下一次循环。
    • 语法:continue 循环名称;循环名称:
  6. this:
    • 表示当前对象的引用。
    • 用于区分局部变量和实例变量,或在构造方法中调用其他构造方法(this(实参))。
    • 不能用于静态方法中。
  7. native:
    • 用于调用 JVM 本地程序。

输入与输出

  1. System.out.println():
    • 控制台输出,println 表示输出并换行。
  2. 键盘输入:
    • 创建键盘扫描器对象:java.util.Scanner s = new java.util.Scanner(System.in);
    • 字符串输入:String user = s.next();
    • 整数输入:int num = s.nextInt();

final 关键字

  1. 修饰类:
    • 类不能被继承。
  2. 修饰方法:
    • 方法不能被重写。
  3. 修饰变量:
    • 变量不能被修改。
    • 修饰的成员变量必须手动赋值。
    • 修饰的引用一旦指向一个对象,就不能指向其他对象,但所指向的内存可以修改。
  4. 常量:
    • 定义常量:public static final 类型 常量名 = 值;
    • 命名规则:全部大写,用下划线分隔。

super 关键字

  1. 作用:
    • 代表当前对象的父类型特征。
    • 用于访问父类的属性、方法或调用父类的构造方法。
  2. 语法:
    • 访问父类属性或方法:super.
    • 调用父类构造方法:super()
  3. 规则:
    • 不能用于静态方法中。
    • 如果父类和子类有同名属性,访问父类属性时不能省略 super
    • 构造方法的第一行如果没有 this()super(),默认会调用 super()

static 关键字

  1. 静态变量:
    • 属于类级别,不依赖于对象,类加载时初始化。
  2. 静态方法:
    • 类级别的方法,不能访问实例变量或实例方法。
  3. 静态代码块:
    • 在类加载时执行,只执行一次。
    • 语法:static {}
  4. 实例代码块:
    • 在构造方法执行之前执行,用于对象初始化。

包与导入

  1. package:
    • 用于管理类,命名规则:公司域名倒序.项目名.模块名.功能名。
    • 语法:package 包名;
  2. import:
    • 用于导入包中的类。
    • 语法:import 包名.类名;import 包名.*;
    • java.lang.* 是核心语言包,无需导入。
  3. 快捷键:
    • Ctrl+Shift+O:自动导入。

访问控制权限修饰符

  1. private:
    • 私有访问权限,只能在本类中访问。
  2. default:
    • 默认访问权限,可以被本包中的其他类访问。
  3. protected:
    • 受保护的访问权限,可以被本包及不同包的子类访问。
  4. public:
    • 公共访问权限,可以在任何地方访问。
  5. 类的修饰符:
    • 类只能使用 public 或默认修饰符(缺省),内部类除外。

总结

  • 类与关键字publicclassstaticthissuper 等关键字的作用与用法。
  • 输入与输出:控制台输出与键盘输入的基本操作。
  • final:用于修饰类、方法、变量,表示不可修改。
  • static:修饰类级别的成员,与对象无关。
  • 包与导入packageimport 的使用及命名规则。
  • 访问控制权限privatedefaultprotectedpublic 的访问范围。

四、Java基础

以下是用户提供的内容的总结:


标识符

  1. 定义:
    • 用户有权命名的单词,包括类名、方法名、常量名、变量名、接口名等。
  2. 命名规则:
    • 类名、接口名: 首字母大写,后面每个单词首字母大写(大驼峰命名法)。
    • 方法名、变量名: 首字母小写,后面每个单词首字母大写(小驼峰命名法)。
    • 常量名: 全部大写,单词间用下划线分隔。

字面值

  • 定义: 数据本身,如数字、字符串等,通常以紫色显示。

变量

  1. 局部变量:
    • 定义在方法体内,没有默认值,必须手动初始化。
    • 生命周期与方法调用一致。
  2. 成员变量:
    • 定义在类体内,有默认值(数值类型为 0,布尔类型为 false,引用类型为 null)。
    • 分为实例变量和静态变量。
  3. 实例变量:
    • 不带 static 关键字,属于对象级别。
    • 必须通过对象引用访问(引用.变量名)。
    • 存储在堆内存中。
  4. 静态变量:
    • static 关键字,属于类级别。
    • 在类加载时初始化,存储在方法区内存中。
    • 通过类名访问(类名.变量名)。

引用

  • 定义: 是一个变量,可以是实例变量或局部变量。
    • 实例变量: 类名 引用 = new 类名();
    • 局部变量: 引用 变量名 = new 引用();

数据类型

  1. 基本数据类型:
    • 整数型: byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节,后缀 L)。
    • 浮点型: float(4 字节)、double(8 字节)。
    • 布尔型: boolean(1 字节)。
    • 字符型: char(2 字节)。
  2. 引用数据类型:
    • 字符串: String,不可变,存储在方法区字符串池中。
  3. 比较:
    • 基本数据类型使用 == 判断相等。
    • 引用数据类型(包括 String)使用 equals 判断相等。

字符编码

  • 发展顺序: ASCII < ISO-8859-1 < GB2312 < GBK < GB18030 < Big5 < Unicode(统一全球编码)。

位运算符

  1. 逻辑异或(^): 两边不一样为真。
  2. 短路与(&&): 左边为假时直接返回假。
  3. 按位与(&): 将操作数转换为二进制后按位与。
  4. 短路或(||): 左边为真时直接返回真。
  5. 左移(<<): 二进制数据左移,相当于乘以 2 的 N 次方。
  6. 右移:
    • 带符号右移(>>): 正数用 0 填充,负数用 1 填充。
    • 无符号右移(>>>): 无论正负都用 0 填充。
  7. 按位取反(~): 将二进制每一位取反,结果为 -(n+1)

方法(函数)

  1. 调用: 类名.方法名(实参列表);
  2. 实例方法: 不带 static,需要对象参与。
  3. 静态方法: 带 static,与对象无关。

定义:

[修饰符列表] 返回值类型 方法名(形参列表){ 方法体;return;// return 后不能跟语句}

方法重载(Overload)

  • 定义: 在同一类中,方法名相同但参数列表不同。
  • 特点: 与返回值类型和修饰符列表无关。

方法递归

  • 定义: 方法调用自身,每次递归都会分配新的内存空间(压栈)。
在这里插入图片描述

示例:

publicstaticintsum(int n){if(n ==1){return1;}return n +sum(n -1);}

方法覆盖(Override)

  1. 定义: 发生在继承关系中,子类重写父类的方法。
  2. 规则:
    • 方法名、返回值类型、形参列表必须与父类一致。
    • 访问权限不能比父类更低,抛出异常不能更多。
  3. 限制:
    • 私有方法、构造方法不能覆盖。
    • 静态方法不存在覆盖。

总结

  • 标识符: 命名规则与用途。
  • 变量: 局部变量、实例变量、静态变量的定义与存储位置。
  • 数据类型: 基本数据类型与引用数据类型的区别。
  • 位运算符: 各种位运算符的作用与用法。
  • 方法: 定义、调用、重载、递归与覆盖的规则与特点。

五、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 表达式:简化了匿名类的使用,使代码更简洁,特别是在实现函数式接口(如 RunnableActionListener)时非常有用。
  • Stream API:提供了强大的集合操作功能,如 mapreduceforEach 等,使得对集合的处理更加高效和简洁。

六、面向对象

面向过程与面向对象的对比

  1. 面向过程
    • 因果关系:关注问题的具体步骤和流程。
    • 具体过程:强调如何一步步解决问题。
    • 耦合度高:各个模块之间依赖性强,修改一个模块可能会影响其他模块。
    • 软件拓展性差:由于耦合度高,系统的扩展和维护较为困难。
  2. 面向对象
    • 分类对象:将问题分解为多个对象,每个对象负责特定的功能。
    • 关系层度低:对象之间的依赖关系较弱,耦合度低。
    • 关注对象功能:关注对象能完成哪些功能,而不是具体的实现步骤。
    • 三大特征
      • 封装性:将复杂的事务封装起来,只保留简单的操作入口。封装后形成独立的对象,提高了代码的复用性、适应性和安全性。
      • 继承性:实现代码复用,最重要的是支持多态和方法覆盖。
      • 多态性:父类型的引用可以指向子类型对象,降低程序耦合度,提高扩展力。

面向对象的分析与设计

  • 面向对象的分析(OOA):分析问题域,识别对象及其关系。
  • 面向对象的设计(OOD):设计对象的结构和行为,定义类及其关系。
  • 面向对象的编程(OOP):使用编程语言实现设计,创建对象并实现其功能。

类与对象

  • :高度抽象的对象的集合,是一个模板。
    • 静态代码块:类加载时执行。
    • 实例代码块:实例化时执行。
    • 静态变量:类级别的变量。
    • 实例变量:对象级别的变量,存储在堆内存中。
    • 构造方法:创建对象时调用,用于初始化实例变量。
    • 静态方法:类级别的方法。
    • 实例方法:对象级别的方法。
    • 成员变量:对象的属性,描述对象的状态。
    • 成员方法:对象的行为,描述对象的动作。
  • 对象:类的具体实例。
    • 创建对象类名 对象名称 = new 类名();
    • 使用对象对象名称.属性名对象名称.方法名()
    • 修改对象引用.变量名 = 值
    • 引用与对象:引用保存了对象的地址,指向堆内存中的对象。多个引用可以指向同一个对象,但一个引用只能指向一个对象。
User u=newUser();Address a=newAddress(); u.addr=a; Print(u.addr.city);A.city=”天津”; Print(u.addr.city);
在这里插入图片描述

封装

  • 私有化属性:使用private关键字将属性私有化。
  • 提供操作入口:通过gettersetter方法提供对属性的访问和修改。
    • 读取属性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表示不是。

以下是关于 抽象类接口 的总结:


抽象类

  1. 定义:
    • 使用 abstract 关键字修饰的类,是类的进一步抽象。
    • 属于引用数据类型。
  2. 特点:
    • 不能使用 privatefinal 修饰。
    • 抽象类可以包含抽象方法和非抽象方法。
    • 抽象类的子类可以是抽象类或非抽象类。
    • 不能实例化(不能创建对象),但可以有构造方法,供子类使用。
  3. 抽象方法:
    • 使用 abstract 关键字修饰,无方法体。
    • 语法:[修饰符列表] abstract 返回值类型 方法名();
    • 包含抽象方法的类一定是抽象类。
  4. 规则:
    • 抽象类不一定有抽象方法,但抽象方法必须出现在抽象类中。
    • 非抽象类继承抽象类时,必须实现所有抽象方法。

语法:

[修饰符列表]abstractclass 类名 {}

接口

  1. 定义:
    • 使用 interface 关键字定义,是完全抽象的(特殊的抽象类)。
    • 属于引用数据类型。
  2. 特点:
    • 接口中只能包含常量和抽象方法(默认 public static finalpublic abstract,修饰符可省略)。
    • 支持多继承,一个接口可以继承多个接口。
    • 接口不能继承抽象类。
  3. 方法类型:
    • 抽象方法: abstract 修饰(可省略)。
    • 默认方法: default 修饰,提供默认实现。
    • 静态方法: static 修饰,通过接口名调用。
  4. 实现:
    • 类通过 implements 关键字实现接口。
    • 非抽象类实现接口时,必须重写所有抽象方法。
    • 一个类可以实现多个接口。
  5. 多态:
    • 接口支持多态:父类型引用指向子类对象
    • 示例:接口名 引用 = new 实现类();
  6. 作用:
    • 解耦合:调用者面向接口调用,实现者面向接口编写实现。
    • 扩展性强:接口+多态可以降低程序耦合度。

语法:

[修饰符列表]interface 接口名 {}

抽象类与接口的区别

特性抽象类接口
抽象程度半抽象(可以包含具体方法)完全抽象(只能包含抽象方法)
构造方法有构造方法,供子类使用无构造方法
继承单继承(一个类只能继承一个抽象类)支持多继承(一个类可以实现多个接口)
内容可以包含抽象方法和非抽象方法只能包含常量和抽象方法
用途抽象行为和数据主要抽象行为
实例化不能实例化不能实例化

开发中的选择

  1. 抽象类:
    • 当多个类有共同的属性和行为,且需要部分具体实现时使用。
    • 适合定义“是什么”(is-a 关系)。
  2. 接口:
    • 当需要定义一组行为规范,且不关心具体实现时使用。
    • 适合定义“能做什么”(like-a 关系)。

示例

实现与继承:

classBirdextendsAnimalimplementsFlyable{@Overridevoidsound(){System.out.println("Chirp...");}@Overridepublicvoidfly(){System.out.println("Flying...");}}

接口:

interfaceFlyable{voidfly();}

抽象类:

abstractclassAnimal{abstractvoidsound();voidsleep(){System.out.println("Sleeping...");}}

总结

  • 面向对象编程通过封装、继承和多态三大特征,提高了代码的复用性、扩展性和维护性。
  • 类与对象是面向对象编程的基础,类是对对象的抽象,对象是类的实例。
  • 封装通过私有化属性和提供操作入口,增强了代码的安全性和可控性。
  • 继承实现了代码的复用,并支持多态和方法覆盖。
  • 多态通过向上转型和向下转型,降低了程序的耦合度,提高了扩展力。
  • 面向抽象编程,而不是面向具体,可以进一步降低耦合度,提高系统的灵活性和可扩展性。
  • 抽象类 用于定义类的共有特征,支持部分具体实现。
  • 接口 用于定义行为规范,支持多继承和解耦合。
  • 在实际开发中,根据需求选择抽象类或接口,合理使用可以提高代码的扩展性和可维护性。

七、类库

源码、字节码与帮助文档

  1. 源码
    • 理解程序:源码是程序员编写的原始代码,用于理解程序的逻辑和功能。
  2. 字节码
    • 程序开发使用:字节码是源码编译后的中间代码,由JVM执行。它是跨平台的,可以在任何支持JVM的系统上运行。
  3. 帮助文档
    • 对开发提供帮助:帮助文档是开发者的参考指南,通常通过javadoc生成。
    • 注意使用版本同一:确保使用的帮助文档与代码版本一致,避免因版本差异导致的错误。

Object类(根类)

Object是Java中所有类的根类,提供了一些核心方法:

  1. protected Object clone()
    • 负责对象克隆,返回对象的副本。
  2. boolean equals(Object obj)
    • 判断两个对象是否相等。默认比较引用地址,通常需要重写以比较对象内容。
  3. int hashCode()
    • 返回对象的哈希代码值,用于哈希表等数据结构。
  4. String toString()
    • 返回对象的字符串表示形式。默认返回类名@哈希值,通常需要重写以提供更有意义的信息。
  5. protected void finalize() throws Throwable
    • 垃圾回收器负责调用,用于对象销毁前的清理工作。
  6. System.gc()
    • 建议启动垃圾回收器,但不保证立即执行。

System类

System类提供了一些系统级别的操作:

  1. System.gc()
    • 建议启动垃圾回收器。
  2. System.out
    • 静态变量,用于控制台输出。
  3. System.out.print()
    • 输出打印不换行。
  4. System.out.println()
    • 换行输出。
  5. System.currentTimeMillis()
    • 获取自1970年1月1日00:00:00到当前系统时间的总毫秒数。
  6. System.exit(0)
    • 退出JVM。

Arrays类

Arrays是数组工具类,提供了一些常用方法:

  1. Arrays.sort(arr)
    • 对数组进行排序。
  2. Arrays.binarySearch(arr, key)
    • 使用二分法查找元素,不存在时返回-1。

String类

String类用于操作字符串,提供了丰富的构造方法和方法:

  1. 构造方法
    • String(byte[] byte):将字节数组转换为字符串。
    • String(char[] char):将字符数组转换为字符串。
    • String(String string):复制字符串。
  2. 常用方法
    • 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

  1. StringBuffer
    • 线程安全,适用于多线程环境。
    • 常用方法:append()reverse()
  2. StringBuilder
    • 非线程安全,性能优于StringBuffer

包装类

包装类用于将基本数据类型转换为对象:

  1. 常用包装类
    • IntegerCharacter等。
  2. 常用方法
    • int intValue():拆箱,将包装类转换为基本类型。
    • static int parseInt(String s):将字符串转换为整数。
在这里插入图片描述

日期相关类

  1. java.util.Date
    • 表示日期和时间。
  2. SimpleDateFormat
    • 用于格式化日期。
    • 常用方法:format()parse()

数字相关类

  1. DecimalFormat
    • 用于格式化数字。
  2. BigDecimal
    • 用于高精度计算,适用于财务数据。
  3. Random
    • 用于生成随机数。

枚举(Enum)

枚举是一种特殊的类,用于定义一组常量:

enumSeason{SPRING,SUMMER,AUTUMN,WINTER}

内部类

  1. 成员内部类
    • 定义在类中,可以访问外部类的所有成员。
  2. 局部内部类
    • 定义在方法中,只能在该方法内访问。
  3. 静态内部类
    • 使用static修饰,只能访问外部类的静态成员。
  4. 匿名内部类
    • 没有名称的内部类,通常用于实现接口或抽象类。

总结

  • 源码是理解程序的基础,字节码是程序运行的关键,帮助文档是开发的指南。
  • Object是Java的根类,提供了对象的基本操作。
  • System类提供了系统级别的操作,如垃圾回收、时间获取等。
  • String类用于操作字符串,提供了丰富的构造方法和方法。
  • StringBufferStringBuilder用于字符串的拼接和修改,前者线程安全,后者性能更优。
  • 包装类用于将基本数据类型转换为对象。
  • 日期相关类用于处理日期和时间。
  • 内部类提供了更灵活的代码组织方式。

八、数组

一维数组

  1. 定义:
    • 数组是引用数据类型,存储在堆内存中。
    • 可以存储各种数据类型,但不能直接存储对象,存储的是对象的引用(内存地址)。
  2. 特点:
    • 数组元素类型统一,最后一个下标为 length - 1
    • 带有 length 属性,用于获取数组长度。
  3. 优点:
    • 查询、查找、检索某个下标元素效率极高(内存连续,类型相同)。
  4. 缺点:
    • 随机增删元素效率较低。
    • 不能存储大数据量。
  5. 定义与初始化:
  6. 遍历:
  7. 方法参数:
  8. main 方法的数组参数:
  9. 存储对象:
  10. 数组扩容:
  11. 数组拷贝:

使用 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,...};

二维数组

  1. 定义:
    • 二维数组是数组的数组,可以看作是一个表格。
  2. 初始化:
  3. 遍历:

使用嵌套 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 数据类型[行数][列数];

总结

  1. 一维数组:
    • 适用于存储一组相同类型的数据。
    • 查询效率高,增删效率低。
    • 可以通过 length 属性获取长度。
    • 支持静态初始化和动态初始化。
  2. 二维数组:
    • 适用于存储表格型数据。
    • 可以看作是一维数组的数组。
    • 支持静态初始化和动态初始化。
  3. 数组的优缺点:
    • 优点:查询效率高,内存连续。
    • 缺点:增删效率低,不能存储大数据量。
  4. 数组的应用场景:
    • 存储一组固定长度的数据。
    • 存储对象引用。
    • 存储表格型数据(二维数组)。

示例

数组扩容:

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 实例:


排序算法

  1. 冒泡排序(Bubble Sort):
    • 思想:重复遍历数组,每次比较相邻元素,如果顺序错误则交换,直到没有需要交换的元素。
    • 时间复杂度:O(n²)。
  2. 选择排序(Selection Sort):
    • 思想:每次从未排序部分选择最小元素,放到已排序部分的末尾。
    • 时间复杂度:O(n²)。
  3. 插入排序(Insertion Sort):
    • 思想:将未排序部分的元素逐个插入到已排序部分的正确位置。
    • 时间复杂度:O(n²)。
  4. 快速排序(Quick Sort):
    • 思想:选择一个基准元素,将数组分为两部分,左边小于基准,右边大于基准,递归排序。
    • 时间复杂度:O(n log n)。
  5. 归并排序(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;}}}}

查找算法

  1. 线性查找(Linear Search):
    • 思想:从头到尾遍历数组,逐个比较,找到目标元素。
    • 时间复杂度:O(n)。
  2. 二分查找(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;}

总结

  1. 排序算法:
    • 冒泡排序:简单但效率低,适合小规模数据。
    • 选择排序:每次选择最小元素,适合小规模数据。
    • 插入排序:适合部分有序的数据。
    • 快速排序:高效,适合大规模数据。
    • 归并排序:稳定且高效,适合大规模数据。
  2. 查找算法:
    • 线性查找:适合无序数据。
    • 二分查找:适合有序数据,效率高。
  3. 选择依据:
    • 数据规模、是否有序、稳定性要求等。

示例

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. 异常处理方式
  1. throws 关键字
    • 在方法声明位置使用,将异常抛给调用者处理。
  2. 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. 自定义异常
  • 步骤
    1. 编写一个类继承 Exception(受检异常)或 RuntimeException(运行时异常)。
    2. 提供两个构造方法:一个无参,一个有参。
    3. 使用 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 检查。
  • 日志记录:使用日志框架(如 Log4jSLF4J)记录异常信息,便于排查问题。

总结

  • 异常分类ErrorException,其中 Exception 分为编译时异常和运行时异常。
  • 处理方式throws 抛给调用者,try-catch-finally 捕获并处理。
  • 自定义异常:继承 ExceptionRuntimeException,提供构造方法,使用 throw 抛出。
  • 最佳实践:明确异常类型,合理使用 finally,避免空指针异常,记录日志。

十一、I/O

I/O(输入/输出)概述

I/O(Input/Output)是指应用程序与外部设备(如磁盘、网络、键盘、显示器等)之间的数据交互。Java通过java.io包提供了丰富的I/O类库,支持文件操作、字节流、字符流等功能。


File类

File类是java.io包中唯一代表磁盘文件本身的对象,用于操作文件和目录。

构造方法
  1. File(String path)
    • 根据路径创建File对象。
  2. File(String parent, String child)
    • 根据父路径和子路径(包括文件名)创建File对象。
  3. File(File parent, String child)
    • 根据File对象表示的父路径和子路径创建File对象。

注意:路径分隔符可以使用\\(Windows)或/(Unix/Linux)。

常用方法
  1. boolean exists()
    • 判断文件或目录是否存在。
  2. boolean delete()
    • 删除文件或目录。
  3. boolean createNewFile()
    • 如果文件不存在,则创建一个新文件。
  4. String getName()
    • 返回文件或目录的名称。
  5. String getPath()
    • 返回文件或目录的路径。
  6. String getAbsolutePath()
    • 返回文件或目录的绝对路径。
  7. boolean canRead()
    • 判断文件是否可读。
  8. boolean canWrite()
    • 判断文件是否可写。
  9. boolean isFile()
    • 判断是否为文件。
  10. boolean isDirectory()
    • 判断是否为目录。
  11. long length()
    • 返回文件内容的长度(字节数)。
  12. String[] list()
    • 返回目录内所有文件和子目录的名称。
  13. File[] listFiles()
    • 返回目录内所有文件和子目录的File对象。
  14. createTempFile(String prefix, String suffix)
    • 创建临时文件。
  15. deleteOnExit()
    • JVM退出时自动删除文件。

字节流

字节流用于处理二进制数据(如图片、音频、视频等),以字节为单位进行读写操作。

在这里插入图片描述
字节输入流(InputStream)

InputStream是字节输入流的抽象类,用于从源(如文件、网络等)读取数据。

常用方法

  1. int read()
    • 逐个字节读取,返回读取的字节值(0-255),如果到达流末尾则返回-1。
  2. int read(byte[] b)
    • 将数据读取到字节数组b中,返回实际读取的字节数。
  3. int read(byte[] b, int off, int len)
    • 从偏移量off开始,读取len个字节到数组b中,返回实际读取的字节数。
  4. void close()
    • 关闭流,释放资源。
字节输出流(OutputStream)

OutputStream是字节输出流的抽象类,用于将数据写入目标(如文件、网络等)。

常用方法

  1. void write(int b)
    • 逐个字节写入。
  2. void write(byte[] b)
    • 将字节数组b中的数据写入。
  3. void write(byte[] b, int off, int len)
    • 从偏移量off开始,写入len个字节。
  4. void flush()
    • 强制将缓冲区中的数据写入目标。
  5. void close()
    • 关闭流,释放资源。
具体实现类
  1. FileInputStream
    • 用于从文件中读取字节数据。
  2. FileOutputStream
    • 用于将字节数据写入文件。

拓展总结

  1. 文件操作
    • 使用File类可以创建、删除、重命名文件,判断文件是否存在,查询文件属性等。
  2. 字节流
    • 字节流适用于处理二进制数据,InputStreamOutputStream是字节流的抽象基类。
    • FileInputStreamFileOutputStream是常用的字节流实现类,用于文件的读写操作。
  3. 流的使用注意事项
    • 使用流时,务必在操作完成后调用close()方法关闭流,释放系统资源。
    • 对于输出流,可以调用flush()方法强制将缓冲区中的数据写入目标。
  4. 临时文件
    • 使用createTempFile()方法可以创建临时文件,deleteOnExit()方法可以确保JVM退出时自动删除临时文件。
  5. 路径处理
    • 路径分隔符可以使用\\(Windows)或/(Unix/Linux),Java会自动处理。
  6. 性能优化
    • 对于大文件的读写,建议使用缓冲区(如BufferedInputStreamBufferedOutputStream)来提高性能。

示例代码

文件操作
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等),能够正确处理多字节字符。


字符流概述

字符流的核心类是ReaderWriter,它们分别是字符输入流和字符输出流的抽象基类。字符流的主要特点包括:

  1. 以字符为单位
    • 字符流以字符为单位读写数据,适合处理文本文件。
  2. 支持字符编码
    • 字符流可以正确处理字符编码,避免乱码问题。
  3. 高效读写
    • 字符流通常与缓冲区结合使用(如BufferedReaderBufferedWriter),提高读写效率。
在这里插入图片描述

字符输入流(Reader)

Reader是字符输入流的抽象类,用于从源(如文件、字符串等)读取字符数据。

常用方法
  1. int read()
    • 读取单个字符,返回字符的Unicode值(0-65535),如果到达流末尾则返回-1。
  2. int read(char[] cbuf)
    • 将字符数据读取到字符数组cbuf中,返回实际读取的字符数。
  3. int read(char[] cbuf, int off, int len)
    • 从偏移量off开始,读取len个字符到数组cbuf中,返回实际读取的字符数。
  4. void close()
    • 关闭流,释放资源。
具体实现类
  1. FileReader
    • 用于从文件中读取字符数据。
  2. BufferedReader
    • 带有缓冲区的字符输入流,提供readLine()方法逐行读取文本。
  3. InputStreamReader
    • 将字节流转换为字符流,支持指定字符编码。

字符输出流(Writer)

Writer是字符输出流的抽象类,用于将字符数据写入目标(如文件、控制台等)。

常用方法
  1. void write(int c)
    • 写入单个字符。
  2. void write(char[] cbuf)
    • 写入字符数组cbuf中的数据。
  3. void write(char[] cbuf, int off, int len)
    • 从偏移量off开始,写入len个字符。
  4. void write(String str)
    • 写入字符串str
  5. void write(String str, int off, int len)
    • 从偏移量off开始,写入len个字符。
  6. void flush()
    • 强制将缓冲区中的数据写入目标。
  7. void close()
    • 关闭流,释放资源。
具体实现类
  1. FileWriter
    • 用于将字符数据写入文件。
  2. BufferedWriter
    • 带有缓冲区的字符输出流,提供newLine()方法写入换行符。
  3. OutputStreamWriter
    • 将字节流转换为字符流,支持指定字符编码。

字符流与字节流的区别

  1. 单位不同
    • 字节流以字节为单位,适合处理二进制数据。
    • 字符流以字符为单位,适合处理文本数据。
  2. 编码支持
    • 字节流不涉及字符编码,直接处理字节数据。
    • 字符流支持字符编码,能够正确处理多字节字符。
  3. 性能优化
    • 字符流通常与缓冲区结合使用,提高读写效率。

示例代码

字符流读写文件
// 写入文件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);}}

总结

  1. 字符流适用场景
    • 处理文本文件、字符串等字符数据。
  2. 核心类
    • ReaderWriter是字符流的抽象基类。
    • FileReaderBufferedReaderFileWriterBufferedWriter是常用的实现类。
  3. 字符编码
    • 使用InputStreamReaderOutputStreamWriter可以指定字符编码,避免乱码问题。
  4. 性能优化
    • 使用BufferedReaderBufferedWriter可以提高读写效率。
  5. 流关闭
    • 使用try-with-resources语法确保流被正确关闭,释放资源。

通过掌握字符流的核心概念和类库,可以高效地处理文本数据的读写操作。

十二、集合

集合是Java中用于存储和管理一组对象的容器。它提供了一种更灵活、更高效的方式来操作数据集合。以下是集合的核心概念和总结:


集合的特点

  1. 容器性质
    • 集合是一个容器,可以容纳其他类型的数据。
    • 集合不能直接存储基本数据类型(如intchar等),也不能直接存储对象,存储的是Java对象的内存地址(引用)。
  2. 数据结构
    • 不同的集合对应不同的数据结构(如数组、链表、哈希表、二叉树等)。
    • 使用不同的集合等同于使用了不同的数据结构。
  3. 包位置
    • 所有的集合类都位于java.util包中。

集合的层次结构

  1. 超级父接口:Iterable<T>
    • 所有集合都是可迭代的,即可以通过迭代器遍历集合中的元素。
    • 方法:Iterator<T> iterator():返回集合的迭代器。
  2. 单个元素集合的父接口:Collection<E>
    • 表示存储单个元素的集合的超级接口。
    • 子接口包括:ListSetQueue等。
  3. 键值对集合的父接口:Map<K,V>
    • 表示存储键值对的集合,独立于Collection体系。

集合的实现类总结

1. List接口的实现类
  • ArrayList
    • 底层是数组,查询快,增删慢。
    • 非线程安全。
  • LinkedList
    • 底层是双向链表,增删快,查询慢。
    • 非线程安全。
  • Vector
    • 底层是数组,线程安全,但效率较低,使用较少。
2. Set接口的实现类
  • HashSet
    • 底层是HashMap,元素存储在HashMapkey部分。
    • 无序且不允许重复。
  • TreeSet
    • 底层是TreeMap,元素存储在TreeMapkey部分。
    • 元素自动按大小顺序排序。
3. Map接口的实现类
  • HashMap
    • 底层是哈希表,非线程安全。
    • 允许null键和null值。
  • Hashtable
    • 底层是哈希表,线程安全,但效率较低,使用较少。
    • 不允许null键和null值。
  • Properties
    • 底层是哈希表,线程安全。
    • keyvalue只能存储字符串(String)。
  • TreeMap
    • 底层是二叉树。
    • key自动按照大小顺序排序。

集合的选择

  1. 需要存储单个元素
    • 如果需要有序且允许重复,使用List
      • 查询多,增删少:ArrayList
      • 增删多,查询少:LinkedList
    • 如果不需要重复元素,使用Set
      • 无序:HashSet
      • 有序:TreeSet
  2. 需要存储键值对
    • 非线程安全:HashMap
    • 线程安全:HashtableProperties
    • 需要排序:TreeMap
  3. 线程安全
    • 如果需要线程安全,可以使用VectorHashtableProperties,但效率较低。
    • 推荐使用Collections.synchronizedList()ConcurrentHashMap等并发集合。

总结

  1. 集合的核心
    • 集合是存储和管理一组对象的容器,存储的是对象的内存地址。
    • 不同的集合对应不同的数据结构,选择合适的集合可以提高程序效率。
  2. 常用集合
    • List:有序且允许重复,常用ArrayListLinkedList
    • Set:无序且不允许重复,常用HashSetTreeSet
    • Map:存储键值对,常用HashMapTreeMapProperties
  3. 线程安全
    • 线程安全的集合有VectorHashtableProperties,但效率较低。
    • 推荐使用并发集合(如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

Interface SortedMap<K,V>

Class TreeMap<K,V> 二叉树 可排序集合(中序遍历)

Key值自定义类需要实现java.long.Comparable接口或者创建比较器对象(类或者匿名内部类)

Class Collections集合工具类

十三、泛型

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 类后无法继承其他类。

启动线程

thread.start();

创建线程对象

MyThread thread =newMyThread();

2. 实现 Runnable 接口
  • 实现方式
    • 编写一个类,实现 java.lang.Runnable 接口。
    • 实现 run() 方法,定义线程执行的任务。
    • 通常使用匿名内部类创建。
  • 特点
    • 更灵活,可以避免单继承的限制。
    • 适合多个线程共享同一个任务。

启动线程

thread.start();

创建线程对象

Runnable task =newMyRunnable();Thread thread =newThread(task);

3. 使用 CallableFuture 接口
  • 实现方式
    • 编写一个类,实现 java.util.concurrent.Callable 接口。
    • 实现 call() 方法,定义线程执行的任务,并返回结果。
  • 创建线程对象
  • 特点
    • call() 方法可以有返回值和抛出异常。
    • 适合需要获取线程执行结果的场景。

获取结果

Integer result = futureTask.get();// 阻塞直到获取结果

启动线程

thread.start();

使用 FutureTask 对象作为 Threadtarget 创建线程:

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 接口灵活,避免单继承限制,适合多线程共享任务无法直接获取线程执行结果
使用 CallableFuture可以获取线程执行结果,支持异常处理,功能更强大使用稍复杂,需要 FutureTask 包装

总结

  • 继承 Thread:适合简单的线程任务,但受限于单继承。
  • 实现 Runnable 接口:更灵活,适合多线程共享任务。
  • 使用 CallableFuture:适合需要获取线程执行结果或处理异常的场景。

根据具体需求选择合适的方式实现多线程编程。

获取当前线程对象、获取线程对象名字、修改线程对象名字

方法名作用
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)是多线程编程中的一种常见问题,指的是两个或多个线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。

死锁代码要会写。一般面试官要求你会写。只有会写的,才会在以后的开发中注意这个事儿。因为死锁很难调试。


死锁的四个必要条件

死锁的发生必须同时满足以下四个条件:

  1. 互斥条件(Mutual Exclusion)
    • 资源一次只能被一个线程占用。
  2. 占有并等待(Hold and Wait)
    • 线程已经占有了至少一个资源,但又申请新的资源,而新的资源被其他线程占用。
  3. 不可抢占(No Preemption)
    • 线程已占有的资源不能被其他线程强行抢占,必须由线程自己释放。
  4. 循环等待(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. 线程1
    • 先获取lock1,然后尝试获取lock2
    • 在获取lock2之前,线程1会休眠100毫秒。
  2. 线程2
    • 先获取lock2,然后尝试获取lock1
    • 在获取lock1之前,线程2会休眠100毫秒。
  3. 死锁发生
    • 线程1持有lock1并等待lock2
    • 线程2持有lock2并等待lock1
    • 两个线程互相等待,导致死锁。

如何避免死锁
  1. 避免嵌套锁
    • 尽量不要在持有一个锁的同时去申请另一个锁。
  2. 按顺序获取锁
    • 如果多个线程需要获取多个锁,确保它们以相同的顺序获取锁。
  3. 使用超时机制
    • 在获取锁时设置超时时间,如果超时则释放已持有的锁并重试。
  4. 使用工具检测
    • 使用工具(如jstack)检测死锁。

死锁的调试与检测
  1. 使用jstack
    • 运行程序后,使用jstack命令查看线程状态,可以检测到死锁。
  2. 日志输出
    • 在代码中添加日志,记录锁的获取和释放情况。
  3. 使用工具
    • 使用IDE(如IntelliJ IDEA)或第三方工具(如VisualVM)检测死锁。

守护线程

在Java中,线程分为两大类:用户线程守护线程。守护线程(Daemon Thread)是一种特殊的线程,它的生命周期依赖于用户线程。当所有的用户线程结束时,守护线程会自动退出。


守护线程的特点
  1. 依赖用户线程
    • 守护线程是为用户线程提供服务的线程。
    • 当所有的用户线程结束时,守护线程会自动退出。
  2. 典型代表
    • 垃圾回收线程(GC)是Java中最典型的守护线程。
  3. 主线程是用户线程
    • main方法所在的线程是用户线程。
  4. 死循环
    • 守护线程通常是一个死循环,持续执行某些后台任务。

守护线程的应用场景
  1. 定时任务
    • 例如,每天00:00自动备份系统数据。
    • 可以使用定时器(如TimerScheduledExecutorService),并将定时任务设置为守护线程。
  2. 后台监控
    • 例如,监控系统资源使用情况、日志清理等。
  3. 垃圾回收
    • Java的垃圾回收线程就是一个守护线程。

守护线程的设置

在Java中,可以通过setDaemon(boolean on)方法将一个线程设置为守护线程:

方法签名说明
void setDaemon(boolean on)ontrue表示将线程设置为守护线程

注意

  • 必须在调用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. 守护线程
    • 守护线程是一个死循环,每隔1秒输出一条消息。
    • 设置为守护线程后,当主线程结束时,守护线程会自动退出。
  2. 主线程
    • 主线程执行5秒后结束。
    • 主线程结束后,守护线程也会自动退出。

守护线程的注意事项
  1. 资源释放
    • 守护线程中不要执行关键任务(如文件写入、数据库操作等),因为它的退出是不可控的。
  2. 线程优先级
    • 守护线程的优先级通常较低,适合执行后台任务。
  3. 生命周期
    • 守护线程的生命周期依赖于用户线程,不能独立存在。

定时器的作用:

间隔特定的时间,执行特定的程序。在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在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 对象的生成方式
  1. 类名.class
    • JVM 将类加载到内存中,但不进行初始化。
    • 返回该类的 Class 对象。
  2. Class.forName("包名.类名")
    • 加载类并默认进行静态初始化。
    • 返回该类的 Class 对象。
  3. Class.forName("包名.类名", false, 类加载器)
    • 第二个参数为 false 时,不进行初始化;为 true 时,进行初始化。
  4. 实例对象.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 对象不一致
    • 如果 AB 的子类,则 A.classB.class 返回的 Class 对象不同。
    • 如果 aA 的实例,则 A.classa.getClass() 返回的 Class 对象一致。

4. Class 类的常用方法
  • getName():返回类的全限定名(包名 + 类名)。
  • getSuperclass():返回类的直接父类的 Class 对象。
  • getInterfaces():返回类实现的所有接口的 Class 数组。
  • isArray():判断该类是否是数组类型。
  • isEnum():判断该类是否是枚举类型。
  • isInterface():判断该类是否是接口。
  • isPrimitive():判断该类是否是基本类型(如 intboolean 等)。
  • isAssignableFrom(Class cls):判断该类是否是 cls 的父类或父接口。
  • getComponentType():如果该类是数组类型,返回数组的组件类型。
  • asSubclass(Class clazz):将当前 Class 对象转换为 clazz 的子类类型。

5. asSubclass 方法的使用
  • 作用:将当前 Class 对象转换为指定类的子类类型。
    • 如果 xxx.xxx.xxxList 的子类,则正常执行;否则抛出 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 对象:存储类的所有信息,是反射机制的核心。
  • 生成方式类名.classClass.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);

Read more

为什么C++26的std::execution内存模型让专家都震惊了?

第一章:C++26 std::execution 内存模型的革命性意义 C++26 中引入的 std::execution 内存模型标志着并发编程范式的重大演进。该模型旨在统一并简化异步操作与执行策略的内存语义,为开发者提供更可预测、更高性能的多线程编程支持。 统一执行上下文的内存可见性 在复杂的并行任务中,不同执行策略(如 std::execution::seq、std::execution::par)对共享数据的访问顺序曾导致难以调试的竞争条件。std::execution 引入了标准化的内存序约束,确保任务在切换执行上下文时保持一致的数据视图。 // 示例:使用 C++26 执行策略启动并行算法 #include <algorithm> #include <execution> #include <vector> std::vector<

By Ne0inhk

SketchUp 3D打印终极指南:手把手教你完成STL导出全流程

SketchUp 3D打印终极指南:手把手教你完成STL导出全流程 【免费下载链接】sketchup-stlA SketchUp Ruby Extension that adds STL (STereoLithography) file format import and export. 项目地址: https://gitcode.com/gh_mirrors/sk/sketchup-stl 你是否曾在SketchUp中精心设计了完美的3D模型,却发现在导出为STL格式时遇到各种问题?从模型破面到单位不匹配,从文件过大到打印失败,这些问题让无数设计师头疼不已。今天,我们就来彻底解决这些困扰,让你从SketchUp建模高手变身为3D打印专家! 🔍 为什么你的SketchUp模型无法直接3D打印? 常见问题深度解析: 模型完整性检查失败 * 开放几何体:模型存在未闭合的面或边线 * 法线方向错误:面的正反方向不统一导致打印异常 * 单位设置混乱:毫米、厘米、英寸混用造成尺寸偏差 文件格式兼容性问题 * 多边形数量超标:过于复杂的模型导致文件体积过大 * 精度设

By Ne0inhk
C++ string 类详解:概念、常用操作与实践(算法竞赛类)

C++ string 类详解:概念、常用操作与实践(算法竞赛类)

🔥个人主页:星轨初途 ❄专栏传送门:C语言,数据结构,C++学习(竞赛类)算法及编程题分享 文章目录 * 前言 * 一、string概念 * 二、string的常见操作和功能 * 1、头文件 * 2、创建字符串 * 3、string字符串的输入 * (1)正常输入(cin) * (2)getline(带空格输入) * 第一种(默认以‘\n’为结束标志) * 第二种(自定义结束标志) * 4、size()——字符串长度 * 5、迭代器(iterator) * begin()和end() * (1)比较 * (2)遍历 * 改变指定字符 * 6、字符串的插入和删除 * (1)插入

By Ne0inhk
【C++笔记】模板初阶

【C++笔记】模板初阶

前言:         C++模板是C++中实现泛型编程的核心工具,允许程序员编写与类型无关的代码,从而提高代码的复用性和灵活性。模板在编译时进行实例化,根据实际使用的类型生成具体的代码,因此不会带来运行时开销。          一、模板基础          1.1 为什么需要模板?          在编写函数或类时,如果希望它们能处理多种数据类型(如int、double、string),传统方法是使用函数重载,但这样会产生大量重复代码或失去类型信息。 模板允许将类型作为参数,编译器根据调用时传入的具体类型生成对应的代码。          场景:需要编写一个求两个数最大值的函数,支持 int、double 和 string(按字典序)。          ①传统方法:函数重载 #include <iostream> #include <string> using namespace std; // 为 int 重载 int max(int

By Ne0inhk