狂命爆肝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. 定义:

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

  3. 实例方法: 不带 static,需要对象参与。

  4. 静态方法: 带 static,与对象无关。


方法重载(Overload)

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

方法递归

  • 定义: 方法调用自身,每次递归都会分配新的内存空间(压栈)。

在这里插入图片描述

  • 示例:

    public static int sum(int n) {if (n == 1) {return 1;}return n + sum(n - 1);
    }
    

方法覆盖(Override)

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

总结

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

五、Java 控制流与 Lambda 表达式

1. 控制流语句
  • If-Else 语句

    if (条件) {// 语句
    } else if (表达式) {// 语句
    } else {// 语句
    }
    
  • Switch 语句

    switch (关键词) {case 关键词:// java语句break;default:// 默认语句
    }
    
  • For 循环

    for (初始表达式; 布尔表达式; 更新循环体) {// 循环体
    }
    
  • 增强 For 循环(For Each)

    for (元素类型 变量名 : 数组或集合) {System.out.println(变量名);
    }
    
  • While 循环

    while (表达式) {// 循环体
    }
    
  • Do-While 循环

    do {// 循环体
    } while (布尔表达式);
    
2. Java 标签
  • 标签用于控制嵌套循环的跳转和中断。
  • 语法label:
  • 用法
    • continue label;:跳过当前循环,继续执行标签处的循环。
    • break label;:结束标签处的循环,执行循环后的代码。
3. Lambda 表达式
  • 实现 Runnable

    // Java 8 之前
    new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Before Java8, too much code for too little to do");}
    }).start();// Java 8 方式
    new Thread(() -> System.out.println("In Java8, Lambda expression rocks !!")).start();
    
  • 事件处理

    // Java 8 之前
    JButton show = new JButton("Show");
    show.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("Event handling without lambda expression is boring");}
    });// Java 8 方式
    show.addActionListener((e) -> {System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
    });
    
  • 列表迭代

    // 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);
    
  • 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);
    
  • 对列表的每个元素应用函数

    List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.", "Canada");
    String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
    System.out.println(G7Countries);
    
  • 计算集合元素的最大值、最小值、总和以及平均值

    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());
    

总结

  • 控制流语句:用于控制程序的执行流程,包括条件判断、循环等。
  • Java 标签:用于控制嵌套循环的跳转和中断。
  • Lambda 表达式:简化了匿名类的使用,使代码更简洁,特别是在实现函数式接口(如 RunnableActionListener)时非常有用。
  • Stream API:提供了强大的集合操作功能,如 mapreduceforEach 等,使得对集合的处理更加高效和简洁。

六、面向对象

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

  1. 面向过程

    • 因果关系:关注问题的具体步骤和流程。
    • 具体过程:强调如何一步步解决问题。
    • 耦合度高:各个模块之间依赖性强,修改一个模块可能会影响其他模块。
    • 软件拓展性差:由于耦合度高,系统的扩展和维护较为困难。
  2. 面向对象

    • 分类对象:将问题分解为多个对象,每个对象负责特定的功能。
    • 关系层度低:对象之间的依赖关系较弱,耦合度低。
    • 关注对象功能:关注对象能完成哪些功能,而不是具体的实现步骤。
    • 三大特征
      • 封装性:将复杂的事务封装起来,只保留简单的操作入口。封装后形成独立的对象,提高了代码的复用性、适应性和安全性。
      • 继承性:实现代码复用,最重要的是支持多态和方法覆盖。
      • 多态性:父类型的引用可以指向子类型对象,降低程序耦合度,提高扩展力。

面向对象的分析与设计

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

类与对象

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

在这里插入图片描述

封装

  • 私有化属性:使用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. 语法:

    [修饰符列表] abstract class 类名 {}
    
  3. 特点:

    • 不能使用 privatefinal 修饰。
    • 抽象类可以包含抽象方法和非抽象方法。
    • 抽象类的子类可以是抽象类或非抽象类。
    • 不能实例化(不能创建对象),但可以有构造方法,供子类使用。
  4. 抽象方法:

    • 使用 abstract 关键字修饰,无方法体。
    • 语法:[修饰符列表] abstract 返回值类型 方法名();
    • 包含抽象方法的类一定是抽象类。
  5. 规则:

    • 抽象类不一定有抽象方法,但抽象方法必须出现在抽象类中。
    • 非抽象类继承抽象类时,必须实现所有抽象方法。

接口

  1. 定义:

    • 使用 interface 关键字定义,是完全抽象的(特殊的抽象类)。
    • 属于引用数据类型。
  2. 语法:

    [修饰符列表] interface 接口名 {}
    
  3. 特点:

    • 接口中只能包含常量和抽象方法(默认 public static finalpublic abstract,修饰符可省略)。
    • 支持多继承,一个接口可以继承多个接口。
    • 接口不能继承抽象类。
  4. 方法类型:

    • 抽象方法: abstract 修饰(可省略)。
    • 默认方法: default 修饰,提供默认实现。
    • 静态方法: static 修饰,通过接口名调用。
  5. 实现:

    • 类通过 implements 关键字实现接口。
    • 非抽象类实现接口时,必须重写所有抽象方法。
    • 一个类可以实现多个接口。
  6. 多态:

    • 接口支持多态:父类型引用指向子类对象
    • 示例:接口名 引用 = new 实现类();
  7. 作用:

    • 解耦合:调用者面向接口调用,实现者面向接口编写实现。
    • 扩展性强:接口+多态可以降低程序耦合度。

抽象类与接口的区别

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

开发中的选择

  1. 抽象类:

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

    • 当需要定义一组行为规范,且不关心具体实现时使用。
    • 适合定义“能做什么”(like-a 关系)。

示例

  1. 抽象类:

    abstract class Animal {abstract void sound();void sleep() {System.out.println("Sleeping...");}
    }
    
  2. 接口:

    interface Flyable {void fly();
    }
    
  3. 实现与继承:

    class Bird extends Animal implements Flyable {@Overridevoid sound() {System.out.println("Chirp...");}@Overridepublic void fly() {System.out.println("Flying...");}
    }
    

总结

  • 面向对象编程通过封装、继承和多态三大特征,提高了代码的复用性、扩展性和维护性。

  • 类与对象是面向对象编程的基础,类是对对象的抽象,对象是类的实例。

  • 封装通过私有化属性和提供操作入口,增强了代码的安全性和可控性。

  • 继承实现了代码的复用,并支持多态和方法覆盖。

  • 多态通过向上转型和向下转型,降低了程序的耦合度,提高了扩展力。

  • 面向抽象编程,而不是面向具体,可以进一步降低耦合度,提高系统的灵活性和可扩展性。

  • 抽象类 用于定义类的共有特征,支持部分具体实现。

  • 接口 用于定义行为规范,支持多继承和解耦合。

  • 在实际开发中,根据需求选择抽象类或接口,合理使用可以提高代码的扩展性和可维护性。

七、类库

源码、字节码与帮助文档

  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)

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

enum Season {SPRING, SUMMER, AUTUMN, WINTER
}

内部类

  1. 成员内部类

    • 定义在类中,可以访问外部类的所有成员。
  2. 局部内部类

    • 定义在方法中,只能在该方法内访问。
  3. 静态内部类

    • 使用static修饰,只能访问外部类的静态成员。
  4. 匿名内部类

    • 没有名称的内部类,通常用于实现接口或抽象类。

总结

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

八、数组

一维数组

  1. 定义:

    • 数组是引用数据类型,存储在堆内存中。
    • 可以存储各种数据类型,但不能直接存储对象,存储的是对象的引用(内存地址)。
  2. 特点:

    • 数组元素类型统一,最后一个下标为 length - 1
    • 带有 length 属性,用于获取数组长度。
  3. 优点:

    • 查询、查找、检索某个下标元素效率极高(内存连续,类型相同)。
  4. 缺点:

    • 随机增删元素效率较低。
    • 不能存储大数据量。
  5. 定义与初始化:

    • 静态初始化:

      数据类型[] 数组名 = {元素1, 元素2, ...};
      
    • 动态初始化:

      数据类型[] 数组名 = new 数据类型[长度];
      
  6. 赋值:

    数组名[下标] =;
    
  7. 遍历:

    • 使用 for 循环或增强 for 循环:

      for (int i = 0; i < 数组名.length; i++) {System.out.println(数组名[i]);
      }
      
  8. 方法参数:

    • 数组可以作为方法的参数:

      void 方法名(数据类型[] 数组名) {}
      
  9. main 方法的数组参数:

    • main 方法的参数是一个字符串数组,用于接收命令行参数:

      public static void main(String[] args) {}
      
  10. 存储对象:

    • 数组可以存储对象的引用:

      类名[] 数组名 = new 类名[长度];
      数组名[0] = new 类名();
      
  11. 数组扩容:

    • 新建一个大数组,然后将原数组拷贝过去:

      int[] newArray = new int[原数组.length * 2];
      System.arraycopy(原数组, 0, newArray, 0, 原数组.length);
      
  12. 数组拷贝:

    • 使用 System.arraycopy 方法:

      System.arraycopy(原数组, 原起点, 目标数组, 目标下标, 长度);
      

二维数组

  1. 定义:

    • 二维数组是数组的数组,可以看作是一个表格。
  2. 语法:

    数据类型[][] 数组名 = new 数据类型[行数][列数];
    
  3. 初始化:

    • 静态初始化:

      数据类型[][] 数组名 = {{元素1, 元素2}, {元素3, 元素4}};
      
    • 动态初始化:

      数据类型[][] 数组名 = new 数据类型[行数][列数];
      
  4. 遍历:

    • 使用嵌套 for 循环:

      for (int i = 0; i < 数组名.length; i++) {for (int j = 0; j < 数组名[i].length; j++) {System.out.println(数组名[i][j]);}
      }
      

总结

  1. 一维数组:

    • 适用于存储一组相同类型的数据。
    • 查询效率高,增删效率低。
    • 可以通过 length 属性获取长度。
    • 支持静态初始化和动态初始化。
  2. 二维数组:

    • 适用于存储表格型数据。
    • 可以看作是一维数组的数组。
    • 支持静态初始化和动态初始化。
  3. 数组的优缺点:

    • 优点:查询效率高,内存连续。
    • 缺点:增删效率低,不能存储大数据量。
  4. 数组的应用场景:

    • 存储一组固定长度的数据。
    • 存储对象引用。
    • 存储表格型数据(二维数组)。

示例

  1. 一维数组:

    int[] arr = {1, 2, 3, 4, 5};
    for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);
    }
    
  2. 二维数组:

    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]);}
    }
    
  3. 数组存储对象:

    Animal[] animals = new Animal[2];
    animals[0] = new Cat();
    animals[1] = new Dog();
    
  4. 数组扩容:

    int[] src = {1, 2, 3};
    int[] dest = new int[src.length * 2];
    System.arraycopy(src, 0, dest, 0, src.length);
    

通过合理使用数组,可以高效地存储和操作数据,但需要注意其增删效率较低的缺点。

九、算法

以下是常见 排序算法查找算法 的思想总结,并附带 Java 实例:


排序算法

  1. 冒泡排序(Bubble Sort):

    • 思想:重复遍历数组,每次比较相邻元素,如果顺序错误则交换,直到没有需要交换的元素。

    • 时间复杂度:O(n²)。

    • Java 实现:

      public static void bubbleSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
      }
      
  2. 选择排序(Selection Sort):

    • 思想:每次从未排序部分选择最小元素,放到已排序部分的末尾。

    • 时间复杂度:O(n²)。

    • Java 实现:

      public static void selectionSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {int minIndex = i;for (int j = i + 1; j < arr.length; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}int temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}
      }
      
  3. 插入排序(Insertion Sort):

    • 思想:将未排序部分的元素逐个插入到已排序部分的正确位置。

    • 时间复杂度:O(n²)。

    • Java 实现:

      public static void insertionSort(int[] arr) {for (int i = 1; i < arr.length; i++) {int key = arr[i];int j = i - 1;while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}
      }
      
  4. 快速排序(Quick Sort):

    • 思想:选择一个基准元素,将数组分为两部分,左边小于基准,右边大于基准,递归排序。

    • 时间复杂度:O(n log n)。

    • Java 实现:

      public static void quickSort(int[] arr, int low, int high) {if (low < high) {int pivot = partition(arr, low, high);quickSort(arr, low, pivot - 1);quickSort(arr, pivot + 1, high);}
      }private static int partition(int[] arr, int low, int high) {int pivot = arr[high];int i = low - 1;for (int j = low; j < high; j++) {if (arr[j] < pivot) {i++;int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}int temp = arr[i + 1];arr[i + 1] = arr[high];arr[high] = temp;return i + 1;
      }
      
  5. 归并排序(Merge Sort):

    • 思想:将数组分成两半,分别排序,然后合并。

    • 时间复杂度:O(n log n)。

    • Java 实现:

      public static void mergeSort(int[] arr, int left, int right) {if (left < right) {int mid = (left + right) / 2;mergeSort(arr, left, mid);mergeSort(arr, mid + 1, right);merge(arr, left, mid, right);}
      }private static void merge(int[] arr, int left, int mid, int right) {int[] temp = new int[right - left + 1];int i = left, j = mid + 1, k = 0;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}while (i <= mid) {temp[k++] = arr[i++];}while (j <= right) {temp[k++] = arr[j++];}for (int p = 0; p < temp.length; p++) {arr[left + p] = temp[p];}
      }
      

查找算法

  1. 线性查找(Linear Search):

    • 思想:从头到尾遍历数组,逐个比较,找到目标元素。

    • 时间复杂度:O(n)。

    • Java 实现:

      public static int linearSearch(int[] arr, int target) {for (int i = 0; i < arr.length; i++) {if (arr[i] == target) {return i;}}return -1;
      }
      
  2. 二分查找(Binary Search):

    • 思想:在有序数组中,每次取中间元素与目标比较,缩小查找范围。

    • 时间复杂度:O(log n)。

    • Java 实现:

      public static int binarySearch(int[] arr, int target) {int left = 0, right = arr.length - 1;while (left <= right) {int mid = (left + right) / 2;if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return -1;
      }
      

总结

  1. 排序算法:

    • 冒泡排序:简单但效率低,适合小规模数据。
    • 选择排序:每次选择最小元素,适合小规模数据。
    • 插入排序:适合部分有序的数据。
    • 快速排序:高效,适合大规模数据。
    • 归并排序:稳定且高效,适合大规模数据。
  2. 查找算法:

    • 线性查找:适合无序数据。
    • 二分查找:适合有序数据,效率高。
  3. 选择依据:

    • 数据规模、是否有序、稳定性要求等。

示例

public class Main {public static void main(String[] args) {int[] arr = {5, 3, 8, 4, 2};bubbleSort(arr);System.out.println("冒泡排序结果: " + Arrays.toString(arr));int[] arr2 = {5, 3, 8, 4, 2};quickSort(arr2, 0, arr2.length - 1);System.out.println("快速排序结果: " + Arrays.toString(arr2));int target = 4;int index = binarySearch(arr2, target);System.out.println("二分查找结果: " + (index != -1 ? "找到,下标为 " + index : "未找到"));}
}

输出:

冒泡排序结果: [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 关键字

    • 在方法声明位置使用,将异常抛给调用者处理。

    • 示例:

      public void readFile() throws IOException {// 可能抛出 IOException 的代码
      }
      
  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 块执行");
      }
      

4. 常用异常方法
  • getMessage():获取异常的简单描述信息(通常是构造方法的参数)。
  • printStackTrace():打印异常的堆栈追踪信息(异步线程中常用)。

5. 自定义异常
  • 步骤

    1. 编写一个类继承 Exception(受检异常)或 RuntimeException(运行时异常)。
    2. 提供两个构造方法:一个无参,一个有参。
    3. 使用 throw 手动抛出异常。
  • 示例

    // 自定义异常类
    public class MyException extends Exception {public MyException() {super();}public MyException(String message) {super(message);}
    }// 使用自定义异常
    public class Test {public static void main(String[] args) {try {throw new MyException("自定义异常发生");} catch (MyException e) {System.out.println(e.getMessage());}}
    }
    

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 = new File("test.txt");
if (!file.exists()) {file.createNewFile(); // 创建文件
}
System.out.println("文件名称: " + file.getName());
System.out.println("文件路径: " + file.getAbsolutePath());
file.delete(); // 删除文件
字节流读写
// 写入文件
try (FileOutputStream fos = new FileOutputStream("output.txt")) {fos.write("Hello, World!".getBytes());fos.flush();
}// 读取文件
try (FileInputStream fis = new FileInputStream("output.txt")) {byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {System.out.println(new String(buffer, 0, len));}
}

通过掌握这些核心概念和类库,可以高效地处理文件操作和字节流读写。

字符流总结

字符流是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 = new FileWriter("output.txt");BufferedWriter bw = new BufferedWriter(fw)) {bw.write("Hello, World!");bw.newLine(); // 写入换行符bw.write("This is a test.");
}// 读取文件
try (FileReader fr = new FileReader("output.txt");BufferedReader br = new BufferedReader(fr)) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}
}
使用指定编码读写文件
// 写入文件(指定编码为UTF-8)
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8");BufferedWriter bw = new BufferedWriter(osw)) {bw.write("你好,世界!");
}// 读取文件(指定编码为UTF-8)
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("output.txt"), "UTF-8");BufferedReader br = new BufferedReader(isr)) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}
}

总结

  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 部分。

Interface Collection

没有使用泛型前可以存储Object的所有子类型

  • Boolean add(E e) 添加元素
  • Object[] toArray() 转化成数组(使用不多)
  • Int size() 返回此集合中元素的数目。
  • Boolean contains(Object o) 如果此集合包含指定的元素(存放在集合中的类型,需要重写equals方法)
  • Void clear() 从此集合中删除所有元素
  • Boolean equals(Object o) 将指定的对象与此集合进行比较以实现相等性(内存地址)
  • Boolean remove(Object o) 从此集合中删除指定元素的单个实例
  • Boolean isEmpty() 如果此集合不包含任何元素(判空)则返回。true

Iterator<E> iterator() ***:**不管存进去什么,拿出来都是Object,取出来还是原类型

返回此集合中元素的迭代器**,Collection通用,Map集合不能用**

只要集合结构发生改变迭代器一定要重新获取

  • default void forEachRemaining(Consumer<? super E> action) 对每个剩余元素执行给定的操作,直到所有元素都已处理完毕或该操作引发异常。
  • Boolean hasNext() 如果迭代具有更多元素,则返回。true
  • Object next() 返回迭代中的下一个元素。(返回object)
  • default void remove() 从基础集合中删除此迭代器返回的最后一个元素(可选操作)。
Interface List 有序可重复,Collection子接口
  • void add(int index, E element) 在此列表中的指定位置插入指定的元素
  • E get(int index) 返回此列表中指定位置处的元素
  • E set(int index, E element) 将此列表中指定位置的元素替换为指定的元素
  • int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含该元素,则返回 -1
  • int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含该元素,则返回 -1。
  • E remove(int index) 删除此列表中指定位置的元素
Class ArrayList 非线程安全数组,初始化容量10,底层object数组

构造方法:

  • ArrayList() 构造初始容量为 10 的空列表(底层先创建了一个长度为0的数组,添加元素是初始化为10,自动扩容1.5倍)
  • ArrayList(int initialCapacity) 构造具有指定初始容量的空列表(建议提前估计,减少扩容)
  • ArrayList(Collection<? extends E> c) 构造一个列表,其中包含指定集合的元素,并按集合的迭代器返回这些元素的顺序排列。

方法:同List方法

Class LinkedList 双向链表,随机增删效率高,检索效率低
Class Vector 线程安全数组,默认10,扩容翻倍**(不经常使用)**

转换:使用集合工具类:java.util.Collections.synchronizedList(集合)

Interface Set 无序不可重复 存储Map的Key

Class HashSet 哈希表(底层HashMap)

需要重写hashCode和equals方法,其他方法参见HashMap

Interface SortedSet 无序不可重复可排序

Class TreeSet 二叉树(底层TreeMap Key部分)无序不可重复可排序

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

class user implements Comparable<user>{      //自定义类需要实现接口int age;public user(int age) {this.age = age;}@Overridepublic String toString() {return "user{" + "age=" + age + '}';}@Override         //重写比较规则public int compareTo(user o) {return this.age-o.age;    //返回==0,value覆盖,返回大于0 到右子树,返回小于0到左子树}
}
Interface Map<K,V> Map主接口(和Collection没有继承关系)

以Key和Value存储数据都是引用数据类型,都存储内存地址,Key是主导

  • V put(K key, V value) 添加键值对(Key元素需要重新hashCode和equals方法)(Key可以为空,只有一个)
  • void clear() 清空Map集合
  • V get(Object key) 通过key获取value(key元素需要重新hashCode和equals方法)
  • boolean containsKey(Object key) 判断Map是否包含某个key(底层equals)
  • boolean containsValue(Object value) 判断Map是否包含某个value(底层equals)
  • boolean isEmpty() 判断Map集合元素个数是否为零
  • Set<K> keySet() 获取Map集合所有的Key(是个set集合)
  • V remove(Object key) 通过key删除键值对
  • Collection<V> values() 获取Map集合中键值对所有value(返回Collection)
  • int size() 获取Map集合所有的键值对个数
Set<Map.Entry<Integer,String>>set1=m.entrySet();       //使用方法
Iterator<Map.Entry<Integer,String>> it=set1.iterator(); //获取迭代器
while (it.hasNext()) {Map.Entry<Integer, String> entry = it.next();System.out.println(entry);     //直接遍历Integer key = entry.getKey();    //获取键String value = entry.getValue();    //获取值System.out.println(key + "=" + value);   //分开遍历for(Map.Entry<Integer,String> node:set1)   //效率较高,适合大数据,直接获取System.out.println(node);     //组合遍历
Class HashMap<K,V> 哈希表 非线程安全(初始化容量16[必须是2的倍数],默认加载因子0.75)

Key元素类型需要重新hashCode和equals方法

JDK8新特性:当单向链表长度超过8后数据结构会变成红黑树数据结构,当红黑树小于6,会变回链表

构造 函数 描述

  • HashMap() 使用默认初始容量 (16) ,默认负载系数 (0.75)
  • HashMap(int initialCapacity) 指定的初始容量,默认负载系数 初始容量必须是2的倍数:达到散列均匀,提高存取效率
  • HashMap(int initialCapacity, float loadFactor) 指定初始容量和负载系数
  • HashMap(Map<? extends K,? extends V> m)
Class Hashtable<K,V> 哈希表 线程安全(synchronized) Key不可以为空*(不常用)**

初始化容量11,默认加载因子0.75f,扩容:原容量*2+1

Class Properties 属性类 继承Hashtable类 仅支持String

  • Object setProperty(String key, String value) 存
  • String getProperty(String key) 取
  • String getProperty(String key, String defaultValue) 当key值为NULL时,返回def的值;当key值不为NULL时,返回key的值
Interface SortedMap<K,V>

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

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

Class Collections 集合工具类
  • synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全)映射。
  • synchronizedList(List list) 返回由指定列表支持的同步(线程安全)列表。**
    **synchronizedCollection(Collection c) 返回由指定集合支持的同步(线程安全)集合
  • sort(List list, Comparator<? super T> c) 根据指定比较器引发的顺序对指定列表进行排序。

十三、泛型

1. 泛型概述
  • 引入时间:JDK 5.0 之后的新特性。

  • 作用

    • 统一集合中元素的类型,避免类型转换错误。
    • 只在程序编译阶段起作用,编译后会进行类型擦除(Type Erasure)。
  • 语法

    • 在创建对象时,前后两段添加泛型类型。

    • 示例:

      List<String> list = new ArrayList<String>();
      

2. 泛型的优点
  • 类型安全:编译时检查类型,避免运行时类型转换错误。
  • 代码复用:可以编写通用的类和方法,适用于多种类型。
  • 代码简洁:减少强制类型转换的代码。

3. 泛型的缺点
  • 导致集合存储缺少多样性:泛型限制了集合中元素的类型,无法存储多种类型的对象。
  • 类型擦除:泛型信息在编译后会被擦除,运行时无法获取泛型的具体类型。

4. 自动推断机制(钻石表达式)
  • 引入时间:JDK 7 新特性。

  • 作用:自动推断泛型类型,简化代码。

  • 语法:只写前面的泛型类型,后面的泛型类型可以省略。

  • 示例

    List<String> list = new ArrayList<>();
    

5. 自定义泛型
  • 泛型类

    • 在定义类时添加 <T>T 是类型参数。

    • 示例:

      public class Box<T> {private T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;}
      }
      
    • 使用:

      Box<String> box = new Box<>();
      box.setValue("Hello");
      String value = box.getValue();
      
  • 泛型方法

    • 在定义方法时添加 <T>T 是类型参数。

    • 示例:

      public <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);}
      }
      
    • 使用:

      Integer[] intArray = {1, 2, 3};
      printArray(intArray);
      

6. 泛型的通配符
  • <?>:表示任意类型。

  • <? extends T>:表示 T 或其子类型(上界通配符)。

  • <? super T>:表示 T 或其父类型(下界通配符)。

  • 示例:

    public void printList(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() 方法,定义线程执行的任务。
  • 创建线程对象

    MyThread thread = new MyThread();
    
  • 启动线程

    thread.start();
    
  • 特点

    • 简单易用,但 Java 是单继承,继承 Thread 类后无法继承其他类。

2. 实现 Runnable 接口
  • 实现方式

    • 编写一个类,实现 java.lang.Runnable 接口。
    • 实现 run() 方法,定义线程执行的任务。
    • 通常使用匿名内部类创建。
  • 创建线程对象

    Runnable task = new MyRunnable();
    Thread thread = new Thread(task);
    
  • 启动线程

    thread.start();
    
  • 特点

    • 更灵活,可以避免单继承的限制。
    • 适合多个线程共享同一个任务。

3. 使用 CallableFuture 接口
  • 实现方式

    • 编写一个类,实现 java.util.concurrent.Callable 接口。
    • 实现 call() 方法,定义线程执行的任务,并返回结果。
  • 创建线程对象

    1. 创建 Callable 实现类的实例:

      Callable<Integer> task = new MyCallable();
      
    2. 使用 FutureTask 包装 Callable 对象:

      FutureTask<Integer> futureTask = new FutureTask<>(task);
      
    3. 使用 FutureTask 对象作为 Threadtarget 创建线程:

      Thread thread = new Thread(futureTask);
      
  • 启动线程

    thread.start();
    
  • 获取结果

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

    • call() 方法可以有返回值和抛出异常。
    • 适合需要获取线程执行结果的场景。

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 中的死锁示例

以下是一个经典的死锁代码示例,展示了两个线程互相等待对方释放锁的情况:

public class DeadlockExample {private static final Object lock1 = new Object();private static final Object lock2 = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread 1: Holding lock 1...");try {Thread.sleep(100); // 模拟操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1: Waiting for lock 2...");synchronized (lock2) {System.out.println("Thread 1: Acquired lock 2!");}}});Thread thread2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread 2: Holding lock 2...");try {Thread.sleep(100); // 模拟操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2: Waiting for lock 1...");synchronized (lock1) {System.out.println("Thread 2: Acquired lock 1!");}}});thread1.start();thread2.start();}
}
代码分析
  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
  • 守护线程中创建的子线程默认也是守护线程。

代码示例

以下是一个守护线程的示例,展示了如何设置守护线程以及它的行为:

public class DaemonThreadExample {public static void main(String[] args) {Thread daemonThread = new Thread(() -> {while (true) {System.out.println("守护线程正在运行...");try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();}}});// 设置为守护线程daemonThread.setDaemon(true);// 启动守护线程daemonThread.start();// 主线程(用户线程)执行任务System.out.println("主线程开始执行...");try {Thread.sleep(5000); // 模拟主线程执行任务} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程执行完毕,程序退出。");}
}
代码分析
  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 对象。

    • 示例:

      Class<?> clazz = String.class;
      
  2. Class.forName("包名.类名")

    • 加载类并默认进行静态初始化。

    • 返回该类的 Class 对象。

    • 示例:

      Class<?> clazz = Class.forName("java.lang.String");
      
  3. Class.forName("包名.类名", false, 类加载器)

    • 第二个参数为 false 时,不进行初始化;为 true 时,进行初始化。

    • 示例:

      Class<?> clazz = Class.forName("java.lang.String", false, ClassLoader.getSystemClassLoader());
      
  4. 实例对象.getClass()

    • 对类进行静态初始化和非静态初始化。

    • 返回运行时实际对象所属类的 Class 对象。

    • 示例:

      String str = "Hello";
      Class<?> clazz = str.getClass();
      

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 对象转换为指定类的子类类型。

  • 示例

    List<String> strList = new ArrayList<>();
    Class<? extends List> strListCast = strList.getClass().asSubclass(List.class);
    
  • 动态加载时的应用

    Class.forName("xxx.xxx.xxx").asSubclass(List.class).newInstance();
    
    • 如果 xxx.xxx.xxxList 的子类,则正常执行;否则抛出 ClassCastException

6. 静态加载与动态加载
  • 静态加载:通过 new ClassName() 加载类,编译时必须提供类的定义。
  • 动态加载:通过 Class.forName("ClassName") 加载类,编译时可以缺席,运行时按需提供。

总结

  • Class 对象:存储类的所有信息,是反射机制的核心。
  • 生成方式类名.classClass.forName()实例对象.getClass()
  • 常用方法getName()getSuperclass()getInterfaces()asSubclass() 等。
  • asSubclass:用于将 Class 对象转换为指定类的子类类型。
  • 静态加载与动态加载:静态加载在编译时提供类定义,动态加载在运行时按需提供。

通过掌握 Class 对象和反射机制,可以在运行时动态操作类的成员,实现灵活的编程。

十六、小游戏(进击的小鸟)

public class StartGame {           //游戏开始类public static void main(String[] args) throws InterruptedException {JFrame jFrame = new JFrame("进击の小鸟");  //创建窗口对象jFrame.setSize(400,600);//窗口大小jFrame.setLocationRelativeTo(null); //窗口相对位置jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设定点击关闭结束程序BirdGame birdGame = new BirdGame();  //初始化游戏对象类
​    jFrame.add(birdGame);         //把创建好的对象加进来
​    jFrame.setVisible(true);       //让窗口可视化
​    birdGame.action();          //地面运动方法}
}
public class Bird {public BufferedImage images[];public BufferedImage image;   //存放小鸟图片public int x;public int y;public int width;public int height;public int index=0;public double speed=0;    //小鸟初始速度public double upspeed=30;  //初始上抛速度public double s=0;      //经过t,发生的位移public double t=0.2;     //发生位移时间public double g=9.8;     //重力加速度public Bird() throws IOException {x=120;y=120;images=new BufferedImage[8];image= ImageIO.read(getClass().getResource("0.png"));width=image.getWidth();height=image.getHeight();for (int i=0;i<images.length;i++) {images[i] = ImageIO.read(getClass().getResource(i+".png"));}}public void fly(){      //小鸟飞飞index++;image=images[index/2%8];}public void upSpeed(){    //鼠标点击游戏屏幕,给小鸟一个初始上抛速度speed=upspeed;}public void distanceChange(){  //实现小鸟速度,位移,纵坐标变化double v=speed; //初始速度s=v*t-g*t*t/2;  //经过t小鸟的位移speed=v-g*t;   //小鸟经过时间t的末速度y=y-(int)s;      //经过时间t后,小鸟的y}
}
public class Column {    //管道类public BufferedImage cImage;public int x;public  int y;public int width;public int height;public  int distance=270;  //两根管道之间的距离public static  int count=0;Random random = new Random();public Column() throws IOException {cImage= ImageIO.read(getClass().getResource("column.png"));x=450+distance*count;width=cImage.getWidth();   //获得管道的宽height=cImage.getHeight();  //高y=-( height/2-random.nextInt(300)-50);count++;}public void step(){x-=5; //让地面往左运动if (x<=-width/2){x=x+distance*2;y=-(height/2-random.nextInt(300)-50) ;//x=400;}}
}
public class Ground {  //地面类public BufferedImage image; //存放地面图片public int x;public int y;public Ground() {try {
​      x=0;
​      y=500;
​      image= ImageIO.read(getClass().getResource("ground.png"));} catch (IOException e) {
​      e.printStackTrace();}}public void step(){
​    x-=1; //让地面往左运动if (x==-100){
​      x=0;}}
}
public class Music implements Runnable {    //音乐类Player player=null;@Overridepublic void run() {InputStream resourceAsStream = this.getClass().getResourceAsStream("2.mp3");try {player=new Player(resourceAsStream);player.play();} catch (JavaLayerException e) {e.printStackTrace();}}public void stopBGM(){if (player!=null)player.close();}
}
public class Score {  //连接对象private String sid;private int score;private String time;public String getSid() {return sid;}public void setSid(String sid) {this.sid = sid;}public int getScore() {return score;}public void setScore(int score) {this.score = score;}public String getTime() {return time;}public void setTime(String time) {this.time = time;}
}
public class ScoreManager {   //jdbc连接static{try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}}public List<Score> selectAllScore(){     //查询方法List<Score> list = new ArrayList<>();try {String sql="select * from score order by time";Connection conn = DriverManager.getConnection("jdbc:mysql://cdb-kthncrwi.bj.tencentcdb.com:10159/flybird?useUnicode=true", "student", "521qianfeng");PreparedStatement pst = conn.prepareStatement(sql);ResultSet resultSet = pst.executeQuery();while (resultSet.next()){Score score = new Score();
​        score.setSid(resultSet.getString("sid"));
​        score.setScore(resultSet.getInt("score"));
​        score.setTime(resultSet.getString("time"));
​        list.add(score);}} catch (SQLException e) {
​      e.printStackTrace();}return list;}public int insertScore(int score) {     //插入方法int num = 0;String sql = "insert into score(sid,score,time) value(?,?,?)";try {Connection conn = DriverManager.getConnection("jdbc:mysql://cdb-kthncrwi.bj.tencentcdb.com:10159/flybird?useUnicode=true", "student", "521qianfeng");PreparedStatement pst = conn.prepareStatement(sql);String sid= UUID.randomUUID().toString();     //随机生成id
​      pst.setString(1,sid);
​      pst.setInt(2,score);Date date = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");     //创建时间类型对象String time=simpleDateFormat.format(date);
​      pst.setString(3,time);
​      num=pst.executeUpdate();} catch (SQLException e) {
​      e.printStackTrace();}return num;}
}
public class BirdGame extends JPanel {   //自定义面板类继承面板类ScoreManager sc=new ScoreManager();public JPanel jp=new JPanel();public BufferedImage bg;         //图片缓冲区(在显示图片前对图片进行操作 eg:.getWidth()宽,.getHeight()高)public BufferedImage startbg;public BufferedImage overbg;public Ground ground;public Bird bird;public Column columns[];public Music music;String file="H:\\Java程序\\小程序\\src\\小鸟\\png\\bg.png";public int state; //表示游戏状态public static final int START=0;   //开始public static final int RUNNING=1;  //运行public static final int GAMEOVER=2; //结束public static int score=0;      //初始积分public BirdGame(){try {state=START;         //游戏初始为游戏开始状态ground=new Ground();     //创建地面类对象,调用地面类构造方法bird = new Bird();columns=new Column[2];music = new Music();for (int i=0;i<columns.length;i++){columns[i]=new Column();}bg= ImageIO.read(getClass().getResource("bg.png")); //读取这张图片并把图片值赋给变量//bg=ImageIO.read(new File(file));//bg=ImageIO.read(new File("src/小鸟/png/bg.png"));startbg=ImageIO.read(getClass().getResource("start.png"));overbg=ImageIO.read(getClass().getResource("gameover.png"));} catch (IOException e) {e.printStackTrace();}}@Overridepublic void paint(Graphics g) {         //绘制一次的画画方法super.paint(g);               //调用画笔g.drawImage(bg,0,0,null);            //绘制背景(最后一个参数为观察者switch (state){case START://绘制游戏开始图片settishi(g);g.drawImage(startbg,0,0,null);break;case RUNNING:for (int i=0;i<columns.length;i++) {
​          g.drawImage(columns[i].cImage, columns[i].x, columns[i].y, null);}break;case GAMEOVER://绘制游戏结束图片settishi2(g);
​        g.drawImage(overbg,0,0,null);break;}
​    g.drawImage(ground.image,ground.x,ground.y,null);  //绘制地面
​    g.drawImage(bird.image,bird.x, bird.y,null);     //绘制小鸟setScore(g);}public boolean isHitGround(){      //撞击地面if (bird.y+bird.height>500){return true;}else {return false;}}public boolean isHitSky(){      //撞击天空if (bird.y<0){return true;}else {return false;}}public boolean isguandao(Column c) {if (bird.x + bird.width >= c.x && c.x + c.width >= bird.x) {   //撞击管道左右if (bird.y <= c.height / 2 + c.y - 72 || bird.y + bird.height >= c.height / 2 + c.y + 72) {return true;} else {return false;}} else {return false;}}public void setScore(Graphics g){               //绘制分数方法Font font = new Font(Font.SERIF, Font.ITALIC, 40);  //罗马字体,斜体,40号g.setFont(font);     //获取字体g.setColor(Color.white);//获取颜色g.drawString(score+"分",40,60);    //画字符串}public void settishi(Graphics g){               //绘制分数方法Font font1 = new Font(Font.SERIF, Font.BOLD, 25);  //罗马字体,斜体,40号g.setFont(font1);     //获取字体g.setColor(Color.black);//获取颜色g.drawString("点击屏幕开始运行",110,400);    //画字符串g.drawString("   制作人---赵嘉盟",120,430);}public void settishi2(Graphics g){               //绘制分数方法Font font2 = new Font(Font.SANS_SERIF, Font.BOLD, 30);  //罗马字体,斜体,40号g.setFont(font2);     //获取字体g.setColor(Color.red);//获取颜色g.drawString("点击屏幕重新开始",100,500);    //画字符串}public void action() throws InterruptedException {    //游戏对象运动方法this.addMouseListener(new BirdMouseListener());   //添加鼠标监听器while (true){switch (state){     //状态不同,对象运动效果不同case START:
​          ground.step();  //调用地面运动方法
​          bird.fly();break;case RUNNING:​          bird.distanceChange();
​          ground.step();  //调用地面运动方法
​          bird.fly();if (isHitGround()||isHitSky()){
​            state=GAMEOVER;break;}for (int i=0;i<columns.length;i++){Column cl=columns[i];
​            cl.step();if (isguandao(cl)){
​              state=GAMEOVER;break;}if (bird.x==cl.x){
​              score++;}}break;case GAMEOVER:
​          music.stopBGM();break;}repaint();  //刷新方法(重新绘制)Thread.sleep(50);  //线程睡眠}}class BirdMouseListener extends MouseAdapter{      //小鸟飞行鼠标控制监听内部类@Overridepublic void mousePressed(MouseEvent e) {super.mousePressed(e);switch (state){case START:
​          state=RUNNING;  //鼠标点击开始运行Thread thread = new Thread(music);
​          thread.start();break;case RUNNING:
​          bird.upSpeed(); //鼠标点击屏幕给小鸟一个初始上抛速度break;case GAMEOVER:
​          sc.insertScore(score);  //向数据库插入分数List<Score> scores = sc.selectAllScore();//查询数据库所有分数String message="";for (Score score1 : scores) {
​            message=message+"时间:"+score1.getTime()+"\n分数:"+score1.getScore()+"\n";}JOptionPane.showConfirmDialog(jp,message,"实时分数",JOptionPane.WARNING_MESSAGE);
​          state=START;   //鼠标点击游戏恢复开始状态​          bird.x=120;
​          bird.y=220;
​          bird.speed=0;Column.count=0;try {
​            columns[0] = new Column();} catch (IOException ex) {
​            ex.printStackTrace();}try {
​            columns[1] = new Column();} catch (IOException ex) {
​            ex.printStackTrace();}
​          score = 0;//给积分初始化for (int i=0;i<columns.length;i++){try {
​              columns[i]=new Column();} catch (IOException ex) {
​              ex.printStackTrace();}}break;}}}
}

十七、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 = new ArrayList<>();setList.add(new String[]{"aa","bb"});setList.add(new String[]{"cc","dd"});setList.add(new String[]{"ee","ff"});//使用map方法setList.stream().map(s->Arrays.stream(s)).forEach(s-> System.out.println("map==" + s));//使用flatMap方法setList.stream().flatMap(s->Arrays.stream(s)).forEach(s-> System.out.println("flatMap==" + s));

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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/89145.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+Swing实现餐厅点餐系统(含课程报告)

基于JavaSwing实现餐厅点餐系统 一、系统介绍二、系统展示1.主页2.点菜3.下单4.结算5.销售情况&#xff08;管理员&#xff09; 三、系统实现四、其他系统五、获取源码 一、系统介绍 该系统针对两个方面的用户&#xff0c;一个是用餐客户&#xff0c;另一个是餐厅管理员。将功…

【DRL】强化学习中的概念和术语

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 深度强化学习&#xff08;Deep Reinforcement Learning, DRL&#xff09;是强化学习&#xff08;RL&#xff09;与深度学习&#xff08;DL&#xff09;…

单向实时通信技术SSE

SSE概述 Server-Sent Events 服务器推送事件&#xff0c;简称 SSE&#xff0c;是一种基于HTTP协议的技术&#xff0c;允许服务端向客户端主动推送请求。 其核心特点是流式传输—— 服务端可将数据分块逐步发送&#xff0c;比如当前大语言模型的流式响应就是将先计算出来的数据…

AI Agent的记忆体系与架构设计

LLM本质上是无状态的模型&#xff0c;每次调用都像一次“短暂失忆”。为了让 AI Agent真正理解上下文、具备个性化交互和任务持续性&#xff0c;引入记忆系统至关重要。本文将从技术与架构角度出发&#xff0c;系统介绍构建短期和长期记忆的最佳实践。 一、AI Agent中的记忆类型…

FastJson的反序列化问题入门

FastJson 简介 他是一个java的依赖库主要是用来进行处理web的json数据&#xff0c;比如就类似于序列化和反序列化 演示 先创建一个类&#xff0c;这个fastjson触发的条件主要就是要处理的类中有 set&#xff0c;get方法 这个方法主要是依赖了 封装思想 导入get , set 方法 …

Lavazza拉瓦萨再度牵手兰博基尼汽车 百年咖啡注入超跑速度

2025年6月12日&#xff0c;继去年首次合作反响热烈之后&#xff0c;有着130年历史的全球咖啡巨头Lavazza拉瓦萨与兰博基尼汽车再度携手开启跨界合作。这不仅是两个传奇品牌的基因共振&#xff0c;更是一场关于咖啡豆与机械美学的深度创新实验。 Lavazza&#xff0c;这个名字在意…

Arduino入门教程:​​​​​​​2、代码基础

飞书文档https://x509p6c8to.feishu.cn/docx/Qyv3dvEIDozdcvxlbkRc2lDdnMc 一、基本程序结构 #include <Arduino.h> void setup() {}void loop() {} //头文件->可以理解为Arduino工具箱 #include <Arduino.h> //初始化函数&#xff0c;只执行一次&#xff0c;…

安卓9.0系统修改定制化____系列 ROM解打包 修改 讲解 导读篇

专栏系列前言&#xff1a; &#x1f49d;&#x1f49d;&#x1f49d;本专栏作者从事rom系统修改以及手机维修 刷机多年。从当年山寨机开始。历经安卓4.--至目前的安卓15.合作伙伴遍及各类工作室以及PDA商家 私人玩友等。在广告机 平板 pda设备 会议机 车机的rom修改中略有经…

免单统计 - 华为OD机试真题(JavaScript题解)

华为OD机试题库《C》限时优惠 9.9 华为OD机试题库《Python》限时优惠 9.9 华为OD机试题库《JavaScript》限时优惠 9.9 针对刷题难&#xff0c;效率慢&#xff0c;我们提供一对一算法辅导&#xff0c; 针对个人情况定制化的提高计划&#xff08;全称1V1效率更高&#xff09;。 看…

pikachu靶场通关笔记25 SQL注入08-布尔盲注(base on boolian 手工注入+脚本注入 两种方法渗透)

目录 一、SQL注入 二、布尔盲注 三、源码分析 四、渗透实战 1、SQL注入探测 &#xff08;1&#xff09;输入已有账户 &#xff08;2&#xff09;输入不存在账户 &#xff08;3&#xff09;输入单引号等可能报错的情况 2、手工注入 &#xff08;1&#xff09;探测数据…

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…

数字IC后端实现之Innovus中各种cell名字前缀物理含义

社区新一期T28 a7core 和T12nm A55数字IC后端实现训练营直播课开始预约报名啦&#xff01; 今天给大家分享下Innovus中各种常见cell命名规则及其物理含义。知道这些信息后&#xff0c;后续我们在debug后端项目问题时就可以更高效地定位到具体问题。做为数字IC后端工程师&#…

腐烂之息-(Breath of Decay VR ) 硬核VR游戏

《腐烂之息》 是一款沉浸式VR生存射击游戏&#xff0c;带你进入一个充满丧尸身影的末日世界。在灾难爆发三年后&#xff0c;你将从培养仓中醒来&#xff0c;面对一个废墟般的世界。作为幸存者&#xff0c;你必须依靠自己的智慧&#xff0c;在这个充满危险的世界中生存、同时揭开…

ChatGPT 辅助 PyTorch 开发:从数据预处理到 CNN 图像识别的全流程优化

技术点目录 第一章、ChatGPT与DeepSeek等大语言模型助力AI编程必备技能详解第二章、Python基础知识串讲第三章、PyTorch简介与环境搭建第五章、ChatGPT和DeepSeek等大语言模型助力统计分析与可视化第六章、ChatGPT和DeepSeek等大语言模型助力前向型神经网络第七章、ChatGPT和De…

js正则表达式使用 test match

文章目录 一、介绍二、案例regex.test(ip)用法ip.match(regex)用法 三、regex.test(ip) 和 ip.match(regex) 区别 一、介绍 正则表达式&#xff08;Regular Expression&#xff0c;简称 regex 或 regexp&#xff09;是一种用于描述字符串模式的工具。它可以用来搜索、匹配、替…

强化学习用于长期异质性效应评估学习笔记(三)

在【实验科学中策略的长期异质性效应量化方案探索&#xff08;一&#xff09;】提到了强化学习估计长期价值&#xff0c;将 A/B 策略看作是策略 π 的不同版本&#xff0c;构造马尔可夫决策过程&#xff08;MDP&#xff09;或部分可观测 MDP&#xff08;POMDP&#xff09;&…

for...in 循环深度解析

在JavaScript开发中&#xff0c;for...in循环是一个常见的语法结构&#xff0c;但它在遍历数组时存在很多潜在问题。这些问题如果不加以注意&#xff0c;可能导致意想不到的bug和性能问题。 for…in 循环的本质 for...in循环是设计用来遍历对象属性的&#xff0c;而不是专门为…

MH2213 32位Arm® Cortex®-M3 Core核心并内嵌闪存和SRAM

MH2213 32位Arm Cortex-M3 Core核心并内嵌闪存和SRAM 概述&#xff1a; MH2213 3 2位的Arm Cortex-M3 Core为实现MCU的需要提供了低成本的平台、缩减的引脚数目、降低的系统功耗&#xff0c;同时提供卓越的计算性能和先进的中断系统响应。 基础功能配表&#xff1a; MH2213 32位…

阿里云ACP云计算备考笔记 (6)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …

SQL注入攻击原理与防御全解析

目录 一、引言 二、SQL 注入原理 2.1 SQL 注入的概念 2.2 SQL 注入产生的原因 2.3 SQL 注入的本质 2.4 SQL 注入的关键点 三、SQL 注入的实现方法 3.1 常见的 SQL 注入场景 3.2 不同类型的 SQL 注入方式 3.3 SQL 注入的一般流程 四、SQL 注入的危害 4.1 数据泄露 …