1 版本特性
1.1 jdk8
00.总结
Lambda:三剑客
Stream:三剑客
Optional:三剑客
---------------------------------------------------------------------------------------------------------
Base64:Java类库的标准
接口默认方法:接口中允许有默认实现的方法
新的日期和时间:Clock类、LocalDate、LocalTime 和 LocalDateTime类
方法引用符:::,用于简洁地引用类的方法或对象的方法
01.Base64
a.介绍
在 Java 7中,我们经常需要使用第三方库就可以进行 Base64 编码。
在 Java 8中,Base64 编码已经成为 Java 类库的标准,实例如下:
b.案例
public class Tester {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64.getEncoder().encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
System.out.println( encoded );
final String decoded = new String(Base64.getDecoder().decode( encoded ), StandardCharsets.UTF_8 );
System.out.println( decoded );
}
}
02.接口默认方法
a.Java8使用两个新概念扩展了接口的含义:默认方法和静态方法。
默认方法:默认方法就是一个在接口里面有了一个实现的方法
默认方法使得开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
b.JDK8前,接口中可以定义变量和方法
public interface MyInterface {
public static final int field1 = 0; // 变量:默认修饰符(public、static、final)
int field2 = 0; // 变量:等价上述写法
public abstract void method1(int a) throws Exception; // 方法:默认修饰符(public、abstract)
void method2(int a) throws Exception; // 方法:等价上述写法
}
c.JDK8后,接口中的默认方法,并提供默认实现,【可以对该默认方法重写】
定义:default
调用:Vehicle.super.print();
作用:新增的默认方法在实现类中直接可用
-----------------------------------------------------------------------------------------------------
在Java8之前,在基于抽象的设计中,接口只能有抽象方法,一个接口有一个或多个实现类;
【若接口要增加某个方法,则所有实现类都要新增这个方法的实现,否则就就不满足接口的约束】
【默认接口方法,就是为解决这一问题,实现类可以不用修改继续使用,并且新增的默认方法在实现类中直接可用】
【妥协:维护现有代码的向后兼容性时,静态方法和默认方法是一种很好的折衷,逐步为接口提供附加功能,而不破坏实现类】
-----------------------------------------------------------------------------------------------------
接口默认方法,扩展接口而不必担心破坏实现类
接口默认方法,缩小了接口和抽象类之间的差异。
接口默认方法,无需创建基类,由实现类自己选择覆盖哪个默认方法实现。
接口默认方法,增强了Java 8中的Collections API以支持lambda表达式。
接口默认方法,默认方法不能为java.lang.Object中的方法,因为Object是所有类的基类,这种写法将毫无意义
-----------------------------------------------------------------------------------------------------
如果子类没有重写父接口默认方法的话,会直接继承父接口默认方法的实现;
如果子类重写父接口默认方法为普通方法,则与普通方法的重写类似;
如果子类(接口或抽象类)重写父接口默认方法为抽象方法,那么所有子类的子类需要实现该方法;
-----------------------------------------------------------------------------------------------------
多个默认方法:一个类实现了多个接口,且这些接口有相同的默认方法,需要【重写默认方法,或指定哪个接口的默认方法】
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
public interface FourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}
-----------------------------------------------------------------------------------------------------
public class Car implements Vehicle, FourWheeler { // 解决办法1:重写默认方法
default void print(){
System.out.println("我是一辆四轮汽车!");
}
}
public class Car implements Vehicle, FourWheeler { // 解决办法2:指定哪个接口的默认方法
public void print(){
Vehicle.super.print();
}
}
d.JDK8后,接口中的静态方法,并提供默认实现,【无法对该静态方法重写】【仅对接口方法可见,实例对象无法访问】
定义:static关键字
调用:Vehicle.blowHorn();
作用:将与相关方法内聚到接口,提高内聚性,无需创建额外对象
-----------------------------------------------------------------------------------------------------
【接口提供一种简单的机制,允许通过将相关的方法内聚在接口中,而不必创建新的对象】
【虽然抽象类,也可以“类似接口,提供静态方法,来提高内举性”,但主要区别在于抽象类可以有构造函数、成员变量和方法】
【推荐:把只和接口相关的静态utility方法放在接口中(提高内聚性),而无需额外创建一些utility类专门处理逻辑】
-----------------------------------------------------------------------------------------------------
接口静态方法,接口的一部分,实例对象无法直接访问
接口静态方法,非常适合提供有效的方法,例如null检查,集合排序等
接口静态方法,不允许被实现类覆盖,来提供安全性
-----------------------------------------------------------------------------------------------------
public interface MyInterface {
default void log(String str) {
if (!isEmpty(str)) {
System.out.println("接口默认log()方法:" + str);
}
}
static boolean isEmpty(String str) {
System.out.println("对“接口默认log()方法”是否为null进行检查");
return str == null ? true : "".equals(str) ? true : false;
}
}
public class Demo implements MyInterface {
public static void main(String[] args) {
Demo demo = new Demo(); // MyInterface接口中,log()调用“静态isEmpty()”
demo.log("");
demo.log("test");
MyInterface.isEmpty(""); // 实例对象无法方法,但可以使用“MyInterface.isEmpty()”
}
}
-----------------------------------------------------------------------------------------------------
对“接口默认log()方法”是否为null进行检查
对“接口默认log()方法”是否为null进行检查
接口默认log()方法:test
03.新的日期和时间
a.介绍
Java 8引入了新的 Date-Time API(JSR 310) 来改进时间、日期的处理!
在旧版的 Java 中,日期时间 API 存在诸多问题,例如:
非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
设计很差:Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类被定义在java.text包中。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
时区处理麻烦:日期类并不提供国际化,没有时区支持,因此 Java 引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
b.java.time包中的关键类和各自的使用例子
Clock类
Clock类使用时区来返回当前的纳秒时间和日期。
Clock可以替代System.currentTimeMillis()和TimeZone.getDefault(),实例如下:
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
LocalDate、LocalTime 和 LocalDateTime类
LocalDate 仅仅包含ISO-8601日历系统中的日期部分,实例如下:
//获取当前日期
final LocalDate date = LocalDate.now();
//获取指定时钟的日期
final LocalDate dateFromClock = LocalDate.now( clock );
System.out.println( date );
System.out.println( dateFromClock );
LocalTime 仅仅包含该日历系统中的时间部分,
//获取当前时间
final LocalTime time = LocalTime.now();
//获取指定时钟的时间
final LocalTime timeFromClock = LocalTime.now( clock );
System.out.println( time );
System.out.println( timeFromClock );
LocalDateTime
LocalDateTime 类包含了 LocalDate 和 LocalTime 的信息,但是不包含 ISO-8601 日历系统中的时区信息,
04.方法引用符:::
a.介绍
在 Java 中,:: 是方法引用操作符,用于简洁地引用类的方法或对象的方法。
它是一种函数式编程的语法糖,经常与 Java 8 引入的流(Streams)和 Optional 一起使用。
b.方法引用形式
a.静态方法引用
格式:ClassName::staticMethod
示例:Math::abs 引用的是 Math 类的静态方法 abs。
b.实例方法引用(特定实例)
格式:instance::instanceMethod
示例:myString::toUpperCase 引用的是 myString 对象的 toUpperCase 方法。
c.实例方法引用(任意实例)
格式:ClassName::instanceMethod
示例:Optional::isPresent 引用的是 Optional 类实例的 isPresent 方法。
d.构造器引用
格式:ClassName::new
示例:ArrayList::new 创建一个新的 ArrayList 实例。
c.示例
a.代码
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Optional<String>> optionalList = Arrays.asList(
Optional.of("Hello"),
Optional.empty(),
Optional.of("World")
);
// 使用方法引用过滤
List<Optional<String>> presentOptionals = optionalList.stream()
.filter(Optional::isPresent) // 方法引用
.collect(Collectors.toList());
System.out.println(presentOptionals);
}
}
b.说明
Optional::isPresent 的作用相当于提供了一个简化写法,等价于 optional -> optional.isPresent()。
filter(Optional::isPresent) 将只保留那些 Optional 不为空的元素。
1.2 jdk11
00.总结
Lambda 参数支持 var 关键字
单文件程序直接运行(无需显式编译)
字符串处理增强(strip、isBlank、lines、repeat)
更现代化的 HTTP Client API(标准化)
扩展了 var 的使用场景
嵌套类的改进(内部类语法增强)
01.Lambda 参数支持 var 关键字
a.介绍
在 JDK 11 中,Lambda 表达式的参数可以使用 var 关键字进行显式的类型声明。
这样可以在 Lambda 表达式中更灵活地使用局部变量类型推断,同时允许添加注解。
b.示例
(var x, var y) -> x + y
c.支持注解
(@NotNull var x, @Nullable var y) -> x + y
d.注意
要么所有 Lambda 参数都使用 var,要么都不使用(不能混用)。
这项改进为代码一致性和可读性提供了更多选择。
02.单文件程序的简化运行
a.介绍
在 JDK 11 中,你可以直接运行一个单文件 Java 源代码文件,而无需显式编译成 .class 文件。
这是为了简化小型程序的开发。
b.示例
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
-----------------------------------------------------------------------------------------------------
直接运行:JVM 会自动编译并执行代码。
java HelloWorld.java
c.注意
这种方式适用于简单的单文件程序,不适合复杂项目。
如果使用的是模块化代码结构,则不能使用这种方式。
03.字符串增强:String 新增 strip、lines 等方法
a.介绍
虽然这些是标准库的改进,但它们直接影响了开发中的代码编写方式,提升了对字符串操作的便利性。
strip(): 去除字符串首尾的空白字符(支持 Unicode 空白字符,比 trim() 更强大)。
isBlank(): 判断字符串是否为空白(包含空格、制表符等)。
lines(): 将字符串按行分割为一个流。
repeat(int count): 将字符串重复指定次数。
b.示例
String str = " Hello Java 11 ";
System.out.println(str.strip()); // "Hello Java 11"
System.out.println(str.isBlank()); // false
System.out.println("".isBlank()); // true
System.out.println("Hello\nWorld".lines().count()); // 2
System.out.println("Hi".repeat(3)); // "HiHiHi"
04.HTTP Client API(标准化)
a.介绍
虽然这属于库层面的改进,但它显著改变了 Java 中发 HTTP 请求的写法,大幅简化了代码。
JDK 11 提供了全新的 HTTP Client API(在 java.net.http 包中),支持 HTTP/2 和异步请求。
b.示例
import java.net.http.*;
import java.net.URI;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
05.var 类型的进一步应用
a.介绍
虽然 var 关键字本身是 JDK 10 引入的,但 JDK 11 对其使用场景进一步扩展,例如在 Lambda 参数中(如上所述)。
b.示例
var list = List.of(1, 2, 3);
for (var item : list) {
System.out.println(item);
}
06.嵌套类的改进(内部类语法增强)
a.介绍
在 JDK 11 中,内部类的访问和编译方式得到了一些底层改进,虽然没有直接语法变化,但提升了某些场景下的兼容性。
1.3 jdk17
00.总结
sealed密封类
Switch的模式匹配,预览版
增强的伪随机数生成器:RandomGenerator
多行字符串文本
01.sealed密封类
a.介绍
密封类是一个新的类型系统功能,允许类的设计者控制哪些其他类可以扩展或实现它。
密封类可以帮助设计者更好地控制继承层次结构。
b.示例
public abstract sealed class Shape permits Circle, Square {
// ...
}
public final class Circle extends Shape {
// ...
}
public final class Square extends Shape {
// ...
}
c.关键点
使用 sealed 关键字声明一个类。
使用 permits 子句指定允许继承该类的具体子类。
子类必须是 final、sealed 或 non-sealed。
02.Switch的模式匹配,预览版
a.介绍
JDK 17 引入了模式匹配用于 switch 表达式和语句的预览功能。这增强了 switch 的灵活性和表达能力。
b.示例
Object obj = ...;
switch (obj) {
case Integer i -> System.out.println("Integer: " + i);
case String s -> System.out.println("String: " + s);
default -> System.out.println("Unknown type");
}
c.关键点
switch 现在可以直接作用于类型匹配。
可以在 case 中使用模式匹配语法。
03.增强的伪随机数生成器:RandomGenerator
a.介绍
虽然这是库层面的改进,但它提供了更灵活和更强大的伪随机数生成器,可以通过新的接口和实现进行使用。
JDK 17 引入了 RandomGenerator 接口,所有随机数生成器(包括新的和现有的)都实现了这个接口
b.示例
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
public class RandomExample {
public static void main(String[] args) {
// 获取默认的随机数生成器
RandomGenerator generator = RandomGenerator.getDefault();
// 生成一些随机数
System.out.println("Random int: " + generator.nextInt());
System.out.println("Random double: " + generator.nextDouble());
System.out.println("Random long: " + generator.nextLong());
// 使用新的 RandomGeneratorFactory 获取具体的生成器实现
RandomGenerator l128x256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom").create();
// 生成一些随机数
System.out.println("Random int (L128X256MixRandom): " + l128x256MixRandom.nextInt());
System.out.println("Random double (L128X256MixRandom): " + l128x256MixRandom.nextDouble());
}
}
c.关键点
RandomGenerator 接口:这是一个新的通用接口,定义了所有随机数生成器应实现的方法。
RandomGeneratorFactory:用于创建不同类型的随机数生成器实例。你可以通过名称获取特定的实现。
新算法:JDK 17 提供了多种新的随机数生成算法,如 L128X256MixRandom,这些算法可以通过 RandomGeneratorFactory 创建。
统一接口:所有的随机数生成器,包括 java.util.Random 和 java.security.SecureRandom,都实现了 RandomGenerator 接口,这使得在不同生成器之间切换变得更加容易。
04.多行字符串文本
a.介绍
Text Blocks 特性在 JDK 13 中作为预览引入,在 JDK 15 中正式发布,JDK 17 继续支持。
这是一个多行字符串文本的表示方法,旨在提高多行字符串的可读性和可维护性。
b.示例1:简单的多行字符串
public class TextBlockExample {
public static void main(String[] args) {
String textBlock = """
This is a multi-line
string using text blocks
in Java 17.
""";
System.out.println(textBlock);
}
}
-----------------------------------------------------------------------------------------------------
This is a multi-line
string using text blocks
in Java 17.
c.示例2:带缩进的多行字符串
多行字符串会自动去除前导的公共空白部分(即缩进),以便代码对齐更方便。
-----------------------------------------------------------------------------------------------------
public class TextBlockExample {
public static void main(String[] args) {
String json = """
{
"name": "John",
"age": 30,
"isDeveloper": true
}
""";
System.out.println(json);
}
}
-----------------------------------------------------------------------------------------------------
{
"name": "John",
"age": 30,
"isDeveloper": true
}
d.示例3:保留换行符
多行字符串会保留换行符和空格,因此你可以直接在文本块中书写格式化的文本。
-----------------------------------------------------------------------------------------------------
public class TextBlockExample {
public static void main(String[] args) {
String html = """
<html>
<body>
<h1>Welcome to Java 17</h1>
<p>This is a paragraph.</p>
</body>
</html>
""";
System.out.println(html);
}
}
-----------------------------------------------------------------------------------------------------
<html>
<body>
<h1>Welcome to Java 17</h1>
<p>This is a paragraph.</p>
</body>
</html>
e.示例4:避免转义字符
多行字符串可以减少转义字符的使用,例如在处理文件路径或特殊字符时
-----------------------------------------------------------------------------------------------------
public class TextBlockExample {
public static void main(String[] args) {
String filePath = """
C:\\Users\\John\\Documents\\Java17\\text-blocks.txt
""";
System.out.println(filePath);
}
}
-----------------------------------------------------------------------------------------------------
C:\Users\John\Documents\Java17\text-blocks.txt
f.示例5:使用换行符 \n 和其他格式控制
多行字符串也可以手动控制换行符的位置,例如在生成格式化的文本时。
-----------------------------------------------------------------------------------------------------
public class TextBlockExample {
public static void main(String[] args) {
String message = """
Hello, %s!
Welcome to Java %d.
""".formatted("Alice", 17);
System.out.println(message);
}
}
-----------------------------------------------------------------------------------------------------
Hello, Alice!
Welcome to Java 17.
g.示例6:多行 SQL 查询
多行字符串特别适合用于书写 SQL 查询,使得代码更加直观。
-----------------------------------------------------------------------------------------------------
public class TextBlockExample {
public static void main(String[] args) {
String sqlQuery = """
SELECT id, name, email
FROM users
WHERE age > 18
ORDER BY name;
""";
System.out.println(sqlQuery);
}
}
-----------------------------------------------------------------------------------------------------
SELECT id, name, email
FROM users
WHERE age > 18
ORDER BY name;
1.4 jdk21
00.总结
a.特性
接口的私有方法
局部变量类型推断(var)
字符串处理的新方法
Switch 表达式增强
多行字符串(Text Block)
模式匹配(Pattern Matching)
Sealed 类
Record 类
字符串模板(String Template)
-----------------------------------------------------------------------------------------------------
增强的异常处理: 对特定异常的捕获和处理可能更加灵活。
泛型的改进: 某些场景下的泛型推导可能更智能。
更多API改进: 某些核心库方法在性能或功能上可能得到优化。
虚拟线程: 解决传统线程的性能和资源消耗问题。
b.提升1:代码简洁性和可读性
a.JDK 8 的局限性
需要手动定义变量类型,多行字符串需要手动拼接。
switch 语句相对冗长。
b.JDK 21 的提升
使用 var 简化变量声明。
多行字符串 """ 避免拼接。
switch 表达式更短更安全。
c.示例
var x = 10;
String message = """
Hello World!
This is JDK 21.
""";
String result = switch (day) {
case "MONDAY" -> "Start of the week";
default -> "Unknown";
};
c.提升2:面向对象设计扩展性
a.JDK 8 的局限性
接口仅支持默认和静态方法。
缺少 sealed 类,类层次结构难以限制。
b.JDK 21 的提升
接口支持私有方法,增强代码复用。
引入 sealed 类,限制子类继承范围。
c.示例
sealed class Animal permits Dog, Cat {}
interface MyInterface {
private void helper() {
System.out.println("Helper logic");
}
}
d.提升3:函数式编程支持增强
a.JDK 8 的局限性
类型推断能力有限,需手动声明变量类型。
Stream 操作写法略显冗长。
b.JDK 21 的提升
在 Lambda 表达式中支持 var。
增强 Stream API,例如字符串的 lines() 方法。
c.示例
BiConsumer<String, String> consumer = (var a, var b) -> System.out.println(a + b);
var lines = "Line1\nLine2".lines().collect(Collectors.toList());
e.提升4:数据结构与模式匹配
a.JDK 8 的局限性
类型检查需手动转换,样板代码多。
缺少原生不可变对象支持。
b.JDK 21 的提升
增强 instanceof,支持模式匹配。
引入 Record,快速定义不可变数据对象。
c.示例
if (obj instanceof Integer num) {
System.out.println(num * 2);
}
public record Point(int x, int y) {}
f.字符串操作能力
a.JDK 8 的局限性
缺少内置多行字符串支持。
字符串拼接容易冗长。
b.JDK 21 的提升
多行字符串(""")简化复杂文本处理。
新增字符串方法(如 repeat())。
c.示例
String text = """
Hello
World
""";
System.out.println(text.repeat(3));
g.开发效率与性能
a.JDK 8 的局限性
样板代码多,开发效率受限。
性能优化不及新版本(如 GC 改进)。
b.JDK 21 的提升
引入现代化特性(var、record 等),减少样板代码。
性能显著优化:如 ZGC 和 G1 垃圾收集器改进。
h.总结
| 影响方面 | JDK 8 | JDK 21
|-------------|---------------------|---------------
| 代码简洁性 | 样板代码多,拼接繁琐 | 现代化语法减少样板代码
| 设计模式支持 | 类继承限制较少 | sealed 类提升安全性
| 函数式编程 | 初步支持 | 更完善,类型推断增强
| 数据结构 | 无 Record | Record 更高效
| 性能优化 | 基础优化 | 垃圾回收和编译性能提升
01.接口的私有方法
a.介绍
接口允许定义私有方法,方便代码复用且保持接口设计清晰。
b.示例
interface MyInterface {
private void myPrivateMethod() {
// 私有方法
}
}
02.局部变量类型推断(var)
a.介绍
减少手动声明变量类型的繁琐性,并支持在 Lambda 表达式中使用 var。
b.示例
var list = new ArrayList<String>();
BiConsumer<String, String> consumer = (var a, var b) -> {
System.out.println("a: " + a + ", b: " + b);
};
03.字符串处理的新方法
a.介绍
isBlank():检查字符串是否为空或仅包含空格。
lines():将多行字符串转换为流。
repeat():将字符串重复指定次数。
b.示例
String multiline = "Line 1\nLine 2\nLine 3";
var lines = multiline.lines().collect(Collectors.toList());
System.out.println(lines); // 输出:[Line 1, Line 2, Line 3]
04.Switch表达式增强
a.介绍
支持返回值,且可以使用更简洁的 Lambda 风格语法。
b.示例
String message = switch (day) {
case "MONDAY" -> "Start of the week";
case "FRIDAY" -> "End of the work week";
default -> "Midweek";
};
b.示例
// 类型匹配 + null处理
String describe = switch(obj) {
case Integer i -> "整数: " + i;
case String s && s.length()>5 -> "长字符串";
case null -> "空对象";
default -> "未知类型";
};
05.多行字符串(Text Block)
a.介绍
使用 """ 定义多行字符串,自动处理换行,减少手动拼接字符串的复杂性。
b.示例
String textBlock = """
Hello, this is a text block!
You can write across multiple lines.
""";
06.模式匹配(Pattern Matching)
a.介绍
语法增强,直接在条件语句中绑定变量并进行类型转换。
b.示例
if (obj instanceof String str) {
System.out.println("String: " + str.length());
}
c.示例
if(obj instanceof String){
String s = (String) obj;
System.out.println(s.length());
}
// 新模式
if(obj instanceof String s){
System.out.println(s.length()); // 自动类型转换
}
07.Sealed 类
a.介绍
通过限制子类的类型范围,增强类层次结构的封装性和安全性
-----------------------------------------------------------------------------------------------------
精细化控制类继承关系
permits明确许可子类清单
解除传统final的过度限制
完美配合模式匹配使用
b.示例
sealed class Animal permits Dog, Cat {}
final class Dog extends Animal {}
final class Cat extends Animal {}
c.示例
// 定义密封图形类
public sealed class Shape
permits Circle, Rectangle, Triangle {...}
-----------------------------------------------------------------------------------------------------
// 子类必须声明为 final/sealed/non-sealed
public final class Circle extends Shape {...}
public non-sealed class Rectangle extends Shape {...}
08.Record 类
a.介绍
用于表示不可变数据对象,自动生成常见方法(如构造器、equals()、hashCode()、toString() 等)
-----------------------------------------------------------------------------------------------------
自动实现final类 + private final字段
适合DTO、配置类等数据容器场景
不可继承/不可变特性
b.示例
public record Point(int x, int y) {}
c.示例
// 传统POJO vs Record
public record User(String name, int age) {}
-----------------------------------------------------------------------------------------------------
// 自动生成:构造方法/equals()/hashCode()/toString()
User user = new User("Alice", 30);
System.out.println(user); // User[name=Alice, age=30]
09.字符串模板(String Template)
a.介绍
提供更简单的字符串插值方式,减少拼接字符串的繁琐性。
b.示例
int age = 30;
String message = STR."Hello, \{name}! You are \{age} years old.";
1.5 jdk24
00.汇总
1.Stream Gatherers
2.未命名模式和变量
3.多文件源代码程序直接运行
4.外部函数和内存API
5.字符串模板
6.有序集合
7.简易Web服务器
8.隐式声明类和实例主方法
9.super(...) 之前的语句
01.Stream Gatherers
a.说明
Stream Gatherers是对Stream API的强大扩展,提供了更灵活的流转换机制
特别适合处理有状态操作和输入输出不对等的场景
b.代码示例
import java.util.List;
import java.util.stream.Gatherers;
public class GatherersExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
// 使用滑动窗口计算平均值
List<Double> averages = numbers.stream()
.gather(Gatherers.windowSliding(3)
.map(window -> window.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0)))
.toList();
System.out.println("滑动窗口平均值: " + averages);
// 输出: [2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
// 去除连续重复元素
List<String> words = List.of("apple", "apple", "banana", "banana", "apple");
List<String> distinct = words.stream()
.gather(Gatherers.distinctConsecutive())
.toList();
System.out.println("去除连续重复后: " + distinct);
// 输出: [apple, banana, apple]
}
}
02.未命名模式和变量
a.说明
这个预览特性允许使用下划线(_)作为占位符,忽略不需要的变量或模式匹配结果
b.代码示例
import java.util.Map;
public class UnnamedVariablesExample {
public static void main(String[] args) {
// 旧方式:必须声明不使用的变量
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 87);
for (var entry : scores.entrySet()) {
String name = entry.getKey();
Integer score = entry.getValue(); // 即使不使用name也必须声明
System.out.println("得分: " + score);
}
// 新方式:使用_忽略不需要的变量
for (var entry : scores.entrySet()) {
String _ = entry.getKey(); // 使用_表示不关心这个值
Integer score = entry.getValue();
System.out.println("得分: " + score);
}
// 在模式匹配中使用
Object obj = "Hello";
if (obj instanceof String _) { // 不需要绑定变量名
System.out.println("这是一个字符串,长度: " + ((String)obj).length());
}
}
}
03.多文件源代码程序直接运行
a.说明
Java 24允许直接运行包含多个源文件的程序,无需预先编译,大大简化了小型项目的开发和运行
b.代码示例
假设有以下两个文件在同一目录中:
public class Main {
public static void main(String[] args) {
System.out.println("主程序启动");
Helper helper = new Helper();
helper.doSomething();
}
}
public class Helper {
public void doSomething() {
System.out.println("辅助功能执行中");
}
}
b.运行方式
现在可以直接运行,无需显式编译:java Main.java
系统会自动查找和编译相关的源文件
04.外部函数和内存API
a.说明
这个特性终于从孵化阶段转为标准功能,让Java可以直接调用本机代码并访问外部内存,无需使用JNI
b.代码示例
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class ForeignMemoryExample {
public static void main(String[] args) throws Throwable {
// 使用内存段API分配本机内存
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocate(100);
// 在本机内存中写入数据
segment.setAtIndex(ValueLayout.JAVA_INT, 0, 42);
// 从本机内存读取数据
int value = segment.getAtIndex(ValueLayout.JAVA_INT, 0);
System.out.println("读取到的值: " + value);
// 调用C标准库函数
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (Arena stringArena = Arena.ofConfined()) {
MemorySegment cString = stringArena.allocateUtf8String("Hello, Native World!");
long length = (long) strlen.invoke(cString);
System.out.println("字符串长度: " + length);
}
}
}
}
05.字符串模板
a.说明
字符串模板是一种新的插值机制,允许在字符串中嵌入表达式,这个特性仍处于预览阶段
b.代码示例
public class StringTemplatesExample {
public static void main(String[] args) {
String name = "Java";
int version = 24;
double rating = 4.7;
// 使用字符串模板
String message = STR."""
欢迎使用\{name} \{version}!
用户评分: \{rating}/5.0
当前时间: \{java.time.LocalTime.now()}
""";
System.out.println(message);
// SQL模板示例 (JEP 459)
String table = "users";
String column = "username";
int limit = 10;
String query = SQL."""
SELECT \{column}
FROM \{table}
WHERE active = true
LIMIT \{limit}
""";
System.out.println("SQL查询: " + query);
}
}
06.有序集合
a.说明
JDK 24引入了新的接口SequencedCollection、SequencedSet和SequencedMap,提供了顺序相关的操作方法
b.代码示例
import java.util.*;
public class SequencedCollectionsExample {
public static void main(String[] args) {
// 有序列表
SequencedCollection<String> fruits = new ArrayList<>(List.of("苹果", "香蕉", "橙子"));
// 获取第一个和最后一个元素
String first = fruits.getFirst(); // 苹果
String last = fruits.getLast(); // 橙子
// 获取反向视图
SequencedCollection<String> reversed = fruits.reversed();
System.out.println("反向顺序: " + reversed); // [橙子, 香蕉, 苹果]
// 有序映射
SequencedMap<Integer, String> ranks = new LinkedHashMap<>();
ranks.put(1, "金牌");
ranks.put(2, "银牌");
ranks.put(3, "铜牌");
// 获取第一个和最后一个条目
Map.Entry<Integer, String> firstEntry = ranks.firstEntry(); // 1=金牌
Map.Entry<Integer, String> lastEntry = ranks.lastEntry(); // 3=铜牌
System.out.println("第一名: " + firstEntry.getValue()); // 金牌
System.out.println("最后一名: " + lastEntry.getValue()); // 铜牌
// 获取键的有序集合
SequencedSet<Integer> keys = ranks.sequencedKeySet();
System.out.println("排名顺序: " + keys); // [1, 2, 3]
}
}
07.简易Web服务器
a.说明
JDK 24将之前作为孵化模块的简易HTTP服务器标准化,可以快速启动一个静态文件服务器
Simple Web Server 是一个内置于 JDK 的轻量级 HTTP 服务器,具有以下特性:
易用性:无需额外安装和配置,只需几行代码即可启动服务器。
轻量级:适用于提供静态文件和简单的 HTTP 服务,不会引入额外的复杂性。
灵活性:支持自定义处理器,可以满足简单的动态内容需求。
b.代码示例
import com.sun.net.httpserver.SimpleFileServer;
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.nio.file.Path;
public class SimpleWebServerExample {
public static void main(String[] args) throws Exception {
// 创建一个简单的文件服务器,指向当前目录
Path root = Path.of("./public");
InetSocketAddress addr = new InetSocketAddress(8080);
HttpServer server = SimpleFileServer.createFileServer(addr, root, SimpleFileServer.OutputLevel.VERBOSE);
System.out.println("服务器已启动: http://localhost:8080/");
server.start();
// 也可以通过命令行直接启动:
// java -m jdk.httpserver -p 8080 -d ./public
}
}
08.隐式声明类和实例主方法
a.说明
这个预览特性让Java的入门更简单,允许省略类声明和静态main方法,直接编写代码
b.传统方式
public class HelloWorld {
public static void main(String[] args) {
System.out.println("你好,世界!");
}
}
c.新方式 (HelloWorld.java)
// 注意:无需声明类
void main() { // 不再需要static和args参数
System.out.println("你好,世界!");
// 可以直接访问实例方法
sayHello("Java 24");
}
void sayHello(String name) {
System.out.println("你好," + name + "!");
}
09.super(...) 之前的语句
a.说明
这个预览特性允许在调用父类构造器之前执行一些语句,提高了代码灵活性
b.代码示例
class ConfigurationException extends Exception {
private final String config;
ConfigurationException(String file, String message) {
// 旧方式:必须先调用super()
super(message);
this.config = file;
}
// 新方式:可以在super之前执行语句
ConfigurationException(String file, int line) {
// 提前计算消息
String message = "错误位于 " + file + " 的第 " + line + " 行";
System.out.println("准备创建异常: " + message);
// 使用计算结果调用父类构造器
super(message);
this.config = file;
}
}
1.6 jdk25
00.汇总
1.结构化并发 (Structured Concurrency)
2.作用域值 (Scoped Values)
3.原始类型模式匹配 (预览)
4.隐式声明类和实例主方法 (第二次预览)
5.字符串模板 (第二次预览)
6.Class-File API
01.结构化并发 (Structured Concurrency)
a.说明
该特性从预览阶段转为正式标准。它通过将并发任务分组到一个结构化的作用域内,极大地简化了并发编程。
如果一个子任务失败,作用域可以自动取消其他子任务,确保程序的健壮性和资源管理的可靠性,是虚拟线程时代的关键API。
b.代码示例
import java.util.concurrent.StructuredTaskScope;
import java.util.function.Supplier;
public class StructuredConcurrencyExample {
public static void main(String[] args) throws Exception {
// 使用 try-with-resources 确保作用域被正确关闭
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// fork() 方法启动一个虚拟线程来执行任务
Supplier<String> userTask = scope.fork(() -> findUser("user-123"));
Supplier<Integer> orderTask = scope.fork(() -> fetchOrderCount("user-123"));
// 等待所有子任务完成,如果有任何一个失败,则抛出异常
scope.join().throwIfFailed();
// 所有任务成功后,获取结果
String user = userTask.get();
int orderCount = orderTask.get();
System.out.println("查询成功: " + user + " 有 " + orderCount + " 个订单。");
}
}
private static String findUser(String id) throws InterruptedException {
Thread.sleep(100); // 模拟网络延迟
return "用户 " + id;
}
private static int fetchOrderCount(String id) throws InterruptedException {
Thread.sleep(150); // 模拟数据库查询
return 42;
}
}
02.作用域值 (Scoped Values)
a.说明
该特性也从预览阶段转为正式标准。它提供了一种在线程内部及子线程间高效、安全地共享不可变数据的方式,旨在替代传统的 `ThreadLocal`。
特别是在使用虚拟线程时,作用域值性能更好且更不易造成内存泄漏。
b.代码示例
import java.util.function.Supplier;
public class ScopedValuesExample {
// 定义一个作用域值,类似于一个不可变的全局变量
private static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
public static void main(String[] args) {
// 在一个特定的作用域内绑定值并运行代码
ScopedValue.where(CURRENT_USER, "张三")
.run(() -> handleRequest());
// 作用域外无法访问该值
System.out.println("作用域外: " + CURRENT_USER.isBound()); // 输出: false
}
public static void handleRequest() {
System.out.println("处理请求,当前用户: " + CURRENT_USER.get());
log();
}
public static void log() {
// 在深层调用栈中依然可以访问
System.out.println("记录日志,操作用户: " + CURRENT_USER.get());
}
}
03.原始类型模式匹配 (预览)
a.说明
这是一个新的预览特性。它扩展了模式匹配(`instanceof` 和 `switch`)的能力,
使其可以直接支持原始数据类型(如 `int`, `double`, `boolean`),
而无需通过它们的包装类(`Integer`, `Double`, `Boolean`)。这让代码更简洁、性能更高。
b.代码示例
public class PrimitiveMatchingExample {
public static void main(String[] args) {
printTypeInfo(123); // int
printTypeInfo(45.67); // double
printTypeInfo("Hello"); // String
printTypeInfo(true); // boolean
}
static void printTypeInfo(Object obj) {
switch (obj) {
// 直接匹配原始类型
case int i -> System.out.println("这是一个整数: " + i);
case double d -> System.out.println("这是一个双精度浮点数: " + d);
case boolean b-> System.out.println("这是一个布尔值: " + b);
// 同样支持对象类型
case String s -> System.out.println("这是一个字符串: " + s);
default -> System.out.println("未知类型");
}
}
}
04.隐式声明类和实例主方法 (第二次预览)
a.说明
该特性进入第二次预览,旨在降低Java的入门门槛。它允许开发者编写不包含显式类声明的`.java`文件。
编译器会自动将其视为一个隐式类的定义,并允许使用更简洁的实例 `main` 方法作为程序入口,让初学者能更快地写出第一个“Hello, World!”程序。
b.代码示例
// 文件名: HelloWorld.java
// 注意:整个文件没有 class HelloWorld { ... } 的包裹
void main() {
System.out.println("你好,简洁的 Java 世界!");
greet("JDK 25"); // 可以直接调用同一个文件中的其他方法
}
String greet(String name) {
String message = "欢迎, " + name + "!";
System.out.println(message);
return message;
}
// 运行方式:
// java HelloWorld.java
05.字符串模板 (第二次预览)
a.说明
该特性进入第二次预览阶段。它提供了一种更直观、更安全的字符串构造方式,通过 `STR` 模板处理器,
可以在字符串中直接嵌入变量和表达式,避免了传统 `+` 拼接的繁琐和 `String.format` 的复杂性。
b.代码示例
import java.time.LocalDate;
import static java.lang.StringTemplate.STR;
public class StringTemplatesPreviewExample {
public static void main(String[] args) {
String name = "Alice";
int age = 30;
double balance = 1234.56;
// 使用 STR 模板处理器
String userInfo = STR."""
用户信息:
姓名: \{name}
年龄: \{age}岁
账户余额: $\{String.format("%.2f", balance)}
今天是: \{LocalDate.now()}
""";
System.out.println(userInfo);
}
}
06.Class-File API
a.说明
这是一个新的标准API,为框架、库和工具的开发者提供了一种标准化的方式来解析、生成和转换Java类文件(字节码)。
在此之前,开发者通常需要依赖ASM、Javassist等第三方库来完成这些任务。现在JDK原生提供了这种能力,使得字节码操作更加可靠和统一。
b.代码示例
// 注意:这是一个面向高级用户和工具开发者的API,示例较为复杂。
// 以下代码仅为概念演示,展示如何使用该API构建一个简单的类。
import java.lang.classfile.*;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.lang.constant.ConstantDescs.*;
public class ClassFileApiExample {
public static void main(String[] args) throws Exception {
// 使用 ClassFile API 在内存中构建一个名为 com.example.HelloWorld 的类
byte[] classBytes = ClassFile.of().build(
ClassDesc.of("com.example.HelloWorld"),
classBuilder -> {
// 添加一个公共的、静态的、无参数的 main 方法
classBuilder.withMethod(
"main",
MethodTypeDesc.of(CD_void, CD_String.arrayType()),
AccessFlags.PUBLIC | AccessFlags.STATIC,
methodBuilder -> {
// 实现方法体: System.out.println("Hello from ClassFile API!");
methodBuilder.withCode(codeBuilder -> {
codeBuilder.getstatic(CD_System, "out", CLASS_PrintStream)
.ldc("Hello from ClassFile API!")
.invokevirtual(CLASS_PrintStream, "println", MethodTypeDesc.of(CD_void, CD_String))
.return_();
});
}
);
}
);
// 将生成的字节码写入 .class 文件
Path outputPath = Path.of("HelloWorld.class");
Files.write(outputPath, classBytes);
System.out.println("成功生成 HelloWorld.class 文件。");
}
}
1.7 jdk8-jdk22
01.常用信息1
a.JDK 8 (2014)
Lambda 表达式:简化代码,使得函数式编程成为可能。
函数式接口:通过 @FunctionalInterface 注解定义。
Stream API:处理集合数据的全新方式。
默认方法:接口中允许有默认实现的方法。
新的日期和时间 API:java.time 包。
Optional 类:避免空指针异常。
Nashorn JavaScript 引擎:替代 Rhino。
PermGen 移除:使用 Metaspace。
b.JDK 9 (2017)
模块系统:Project Jigsaw,模块化应用程序。
JShell:交互式编程工具(REPL)。
改进的 JDK 工具:如 jlink 创建自定义运行时镜像。
流增强:添加 takeWhile, dropWhile, iterate 等方法。
私有接口方法:接口中可以有私有方法。
c.JDK 10 (2018)
局部变量类型推断:var 关键字。
垃圾收集器改进:引入 G1 作为默认 GC。
类数据共享 (CDS):更好的启动时间和内存占用。
d.JDK 11 (2018)
Lambda 参数的局部变量语法:使用 var。
HttpClient:新的标准 HTTP 客户端 API。
字符串处理增强:如 strip, repeat, lines, isBlank 方法。
嵌套基类:Nest-Based Access Control。
e.JDK 12 (2019)
Switch 表达式 (预览):简化 switch 语句的写法。
新的垃圾收集器:Shenandoah(实验)。
常量类:JEP 334: JVM Constants API。
f.JDK 13 (2019)
文本块 (预览):多行字符串字面量。
Switch 表达式 (第二预览):增强的 switch 表达式。
g.JDK 14 (2020)
记录类 (预览):简化数据载体类的编写。
文本块 (第二预览)。
Switch 表达式:正式引入。
NVM 提升:Non-Volatile Mapped Byte Buffers。
h.JDK 15 (2020)
隐藏类:Hidden Classes,用于框架生成类。
文本块:正式引入。
ZGC 改进:Z Garbage Collector 支持卸载类和其他改进。
密封类 (预览):Sealed Classes,限制继承。
i.JDK 16 (2021)
记录类:正式引入。
密封类 (第二预览)。
外部内存访问 API (孵化器):安全的内存访问。
Unix-Domain Socket Channels:增强与操作系统的交互。
j.JDK 17 (2021)
密封类:正式引入。
外部函数和内存 API (孵化器):高级的内存和函数访问。
封装的 JDK 内部 API:更严格的封装。
新垃圾收集器:ZGC 和 Shenandoah 成为正式功能。
新版本计划:LTS(长期支持)版本。
k.JDK 18 (2022)
UTF-8:默认字符集改为 UTF-8。
Simple Web Server:简单的内置 Web 服务器,主要用于测试和开发。
Vector API (第三次孵化器):用于高性能计算的向量操作。
Code Snippets in Java API Documentation:在 Javadoc 中支持代码片段。
l.JDK 19 (2022)
Virtual Threads (预览):轻量级线程,简化并发编程。
Structured Concurrency (孵化器):简化多线程处理和错误管理。
Foreign Function & Memory API (第二次预览):与外部内存和本地代码交互。
Pattern Matching for switch (第三次预览):增强的模式匹配功能。
m.JDK 20 (2023)
Virtual Threads (第二次预览)。
Structured Concurrency (第二次孵化器)。
Pattern Matching for switch (第四次预览)。
Record Patterns (预览):更强大的模式匹配支持。
o.JDK 21 (2023)
Virtual Threads:正式引入,简化高并发应用的开发。
Pattern Matching for switch:正式引入,增强 switch 语句的灵活性。
Record Patterns:正式引入,简化数据解构和访问。
Sequenced Collections:新的集合接口,保证顺序访问。
String Templates (预览):简化字符串拼接和格式化。
Scoped Values (孵化器):用于线程局部变量的更安全替代。
p.JDK 22 (计划中)
Garbage Collection Improvements:进一步优化垃圾收集器。
Foreign Function & Memory API:进一步改进外部内存和函数访问。
Vector API:继续优化向量 API,提升性能。
02.常用信息2
a.JDK9 - 集合、Stream 和 Optional更新方法
a.在集合上,Java 9 增加 了 List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合
List.of();
List.of("Hello", "World");
List.of(1, 2, 3);
Set.of();
Set.of("Hello", "World");
Set.of(1, 2, 3);
Map.of();
Map.of("Hello", 1, "World", 2);
b.Stream 中增加了新的方法 ofNullable()、dropWhile()、takeWhile() 以及 iterate() 方法的重载方法。
Java 9 中的 ofNullable() 方 法允许我们创建一个单元素的 Stream,可以包含一个非空元素,也可以创建一个空 Stream。 而在 Java 8 中则不可以创建空的 Stream 。
Stream<String> stringStream = Stream.ofNullable("Java");
System.out.println(stringStream.count());// 1
Stream<String> nullStream = Stream.ofNullable(null);
System.out.println(nullStream.count());//0
-------------------------------------------------------------------------------------------------
takeWhile() 方法可以从 Stream 中依次获取满足条件的元素,直到不满足条件为止结束获取。
List<Integer> integerList = List.of(11, 33, 66, 8, 9, 13);
integerList.stream().takeWhile(x -> x < 50).forEach(System.out::println);// 11 33
-------------------------------------------------------------------------------------------------
dropWhile() 方法的效果和 takeWhile() 相反。
List<Integer> integerList2 = List.of(11, 33, 66, 8, 9, 13);
integerList2.stream().dropWhile(x -> x < 50).forEach(System.out::println);// 66 8 9 13
-------------------------------------------------------------------------------------------------
iterate() 方法的新重载方法提供了一个 Predicate 参数 (判断条件)来决定什么时候结束迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
}
// 新增加的重载方法
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) {
}
-------------------------------------------------------------------------------------------------
两者的使用对比如下,新的 iterate() 重载方法更加灵活一些。
c.Optional 类中新增了 ifPresentOrElse()、or() 和 stream() 等方法
ifPresentOrElse() 方法接受两个参数 Consumer 和 Runnable ,如果 Optional 不为空调用 Consumer 参数,为空则调用 Runnable 参数。
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
Optional<Object> objectOptional = Optional.empty();
objectOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Empty!!!"));// Empty!!!
-------------------------------------------------------------------------------------------------
or() 方法接受一个 Supplier 参数 ,如果 Optional 为空则返回 Supplier 参数指定的 Optional 值。
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
Optional<Object> objectOptional = Optional.empty();
objectOptional.or(() -> Optional.of("java")).ifPresent(System.out::println);//java
b.JDK9 进程 API
a.介绍
Java 9 增加了 ProcessHandle 接口,可以对原生进程进行管理,尤其适合于管理长时间运行的进程。
在使用 ProcessBuilder 来启动一个进程之后,可以通过 Process.toHandle()方法来得到一个
ProcessHandl e 对象的实例。通过 ProcessHandle 可以获取到由 ProcessHandle.Info 表 示的进程的基本信息,
如命令行参数、可执行文件路径和启动时间等。ProcessHandle 的 onExit()方法返回一个 CompletableFuture对象,
可以在进程结束时执行自定义的动作。 如下代码中给出了进程 API 的使用示例。
b.示例
final ProcessBuilder processBuilder = new ProcessBuilder("top").inheritIO();
final ProcessHandle processHandle = processBuilder.start().toHandle();
processHandle.onExit().whenCompleteAsync((handle, throwable) -> {
if (throwable == null) {
System.out.println(handle.pid());
} else {
throwable.printStackTrace();
}
});
c.JDK9 变量句柄
a.介绍
变量句柄是一个变量或一组变量的引用,包括静态域,非静态域,数组元素和堆外数据结构中的组成部分等。
变量句柄的含义类似于已有的方法句柄。变量句柄由 Java 类 java.lang.invoke.VarHandle 来表示。
可以使用类 java.lang.invoke.MethodHandles.Lookup 中的静态工厂方法来创建 VarHandle 对象。
通过变量句柄,可以在变量上进行各种操作。这些操作称为访问模式。不同的访问模式尤其在内存排序上的不同语义。
目前一共有 31 种 访问模式,而每种访问模式都 在 VarHandle 中 有对应的方法。
这些方法可以对变量进行读取、写入、原子更新、数值原子更新和比特位原子操作等。
VarHandle 还可以用来访问数组中的单个元素,以及把 byte[]数组 和 ByteBuffer 当成是不同原始类型的数组来访问。
b.示例
public class VarHandleTest {
private HandleTarget handleTarget = new HandleTarget();
private VarHandle varHandle;
@Before
public void setUp() throws Exception {
this.handleTarget = new HandleTarget();
this.varHandle = MethodHandles
.lookup()
.findVarHandle(HandleTarget.class, "count", int.class);
}
@Test
public void testGet() throws Exception {
assertEquals(1, this.varHandle.get(this.handleTarget));
assertEquals(1, this.varHandle.getVolatile(this.handleTarget));
assertEquals(1, this.varHandle.getOpaque(this.handleTarget));
assertEquals(1, this.varHandle.getAcquire(this.handleTarget));
}
}
d.JDK9-IO新特性
a.介绍
类 java.io.InputStream 中增加了新的方法来读取和复制 InputStream 中包含的数据。
readAllBytes:读取 InputStream 中的所有剩余字节。
readNBytes: 从 InputStream 中读取指定数量的字节到数组中。
transferTo:读取 InputStream 中的全部字节并写入到指定的 OutputStream 中 。
b.示例
public class TestInputStream {
private InputStream inputStream;
private static final String CONTENT = "Hello World";
@Before
public void setUp() throws Exception {
this.inputStream =
TestInputStream.class.getResourceAsStream("/input.txt");
}
@Test
public void testReadAllBytes() throws Exception {
final String content = new String(this.inputStream.readAllBytes());
assertEquals(CONTENT, content);
}
@Test
public void testReadNBytes() throws Exception {
final byte[] data = new byte[5];
this.inputStream.readNBytes(data, 0, 5);
assertEquals("Hello", new String(data));
}
@Test
public void testTransferTo() throws Exception {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
this.inputStream.transferTo(outputStream);
assertEquals(CONTENT, outputStream.toString());
}
}
c.说明
ObjectInputFilter 可以对 ObjectInputStream 中 包含的内容进行检查,来确保其中包含的数据是合法的。
可以使用 ObjectInputStream 的方法 setObjectInputFilter 来设置。ObjectInputFilter 在 进行检查时,
可以检查如对象图的最大深度、对象引用的最大数量、输入流中的最大字节数和数组的最大长度等限制,
也可以对包含的类的名称进行限制。
e.JDK9 - 改进应用安全性能
a.介绍
Java 9 新增了 4 个 SHA-3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 SHA3-512。
另外也增加了通过 java.security.SecureRandom 生成使用 DRBG 算法的强随机数。
如下代码中给出了 SHA-3 哈希算法的使用示例。
b.示例
import org.apache.commons.codec.binary.Hex;
public class SHA3 {
public static void main(final String[] args) throws NoSuchAlgorithmException {
final MessageDigest instance = MessageDigest.getInstance("SHA3-224");
final byte[] digest = instance.digest("".getBytes());
System.out.println(Hex.encodeHexString(digest));
}
}
f.JDK10 - 根证书认证
a.介绍
自 Java 9 起在 keytool 中加入参数 -cacerts ,可以查看当前 JDK 管理的根证书。
而 Java 9 中 cacerts 目录为空,这样就会给开发者带来很多不便。
从 Java 10 开始,将会在 JDK 中提供一套默认的 CA 根证书。
作为 JDK 一部分的 cacerts 密钥库旨在包含一组能够用于在各种安全协议的证书链中建立信任的根证书。
但是,JDK 源代码中的 cacerts 密钥库至目前为止一直是空的。
因此,在 JDK 构建中,默认情况下,关键安全组件(如 TLS)是不起作用的。
要解决此问题,用户必须使用一组根证书配置和 cacerts 密钥库下的 CA 根证书。
g.JDK11 - 标准 HTTP Client 升级
a.介绍
Java 11 对 Java 9 中引入并在 Java 10 中进行了更新的 Http Client API 进行了标准化,
在前两个版本中进行孵化的同时,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。
-------------------------------------------------------------------------------------------------
新版 Java 中,Http Client 的包名由 jdk.incubator.http 改为 java.net.http,
该 API 通过 CompleteableFutures 提供非阻塞请求和响应语义,可以联合使用以触发相应的动作,
并且 RX Flo w 的概念也在 Java 11 中得到了实现。现在,在用户层请求发布者和响应发布者与底层套接字之间追踪数据流更容易了。
这降低了复杂性,并最大程度上提高了 HTTP/1 和 HTTP/2 之间的重用的可能性。
-------------------------------------------------------------------------------------------------
Java 11 中的新 Http Client API,提供了对 HTTP/2 等业界前沿标准的支持,同时也向下兼容 HTTP/1.1,
精简而又友好的 API 接口,与主流开源 API(如:Apache HttpClient、Jetty、OkHttp 等)类似甚至拥有更高的性能。
与此同时它是 Java 在 Reactive-Stream 方面的第一个生产实践,其中广泛使用了 Java Flow API,
终于让 Java 标准 HTTP 类库在扩展能力等方面,满足了现代互联网的需求,是一个难得的现代 Http/2 Client API 标准的实现,
Java 工程师终于可以摆脱老旧的 HttpURLConnection 了。下面模拟 Http GET 请求并打印返回内容:
b.示例
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
h.支持 TLS 1.3 协议
a.介绍
略
i.JDK13 - Socket API 重构
a.介绍
Java 中的 Socket API 已经存在了二十多年了,尽管这么多年来,一直在维护和更新中,
但是在实际使用中遇到一些局限性,并且不容易维护和调试,所以要对其进行大修大改,
才能跟得上现代技术的发展,毕竟二十多年来,技术都发生了深刻的变化。
-------------------------------------------------------------------------------------------------
Java 13 为 Socket API 带来了新的底层实现方法,并且在 Java 13 中是默认使用新的 Socket 实现,
使其易于发现并在排除问题同时增加可维护性。
-------------------------------------------------------------------------------------------------
Java Socket API(java.net.ServerSocket 和 java.net.Socket)包含允许监听控制服务器和发送数据的套接字对象。
可以使用 ServerSocket 来监听连接请求的端口,一旦连接成功就返回一个 Socket 对象,可以使用该对象读取发送的数
据和进行数据写回操作,而这些类的繁重工作都是依赖于 SocketImpl 的内部实现,服务器的发送和接收两端都基于 SOCKS 进行实现的。
-------------------------------------------------------------------------------------------------
在 Java 13 之前,通过使用 PlainSocketImpl 作为 SocketImpl 的具体实现。
Java 13 中的新底层实现,引入 NioSocketImpl 的实现用以替换 SocketImpl 的 PlainSocketImpl 实现,
此实现与 NIO(新 I/O)实现共享相同的内部基础结构,并且与现有的缓冲区高速缓存机制集成在一起,
因此不需要使用线程堆栈。除了这些更改之外,还有其他一些更便利的更改,如使用 java.lang.ref.Cleaner
机制来关闭套接字(如果 SocketImpl 实现在尚未关闭的套接字上被进行了垃圾收集),以及在轮询时套接字处于
非阻塞模式时处理超时操作等方面。
b.示例
为了最小化在重新实现已使用二十多年的方法时出现问题的风险,在引入新实现方法的同时,
之前版本的实现还未被移除,可以通过使用下列系统属性以重新使用原实现方法:
-Djdk.net.usePlainSocketImpl = true
-------------------------------------------------------------------------------------------------
另外需要注意的是,SocketImpl 是一种传统的 SPI 机制,同时也是一个抽象类,并未指定具体的实现,
所以,新的实现方式尝试模拟未指定的行为,以达到与原有实现兼容的目的。
但是,在使用新实现时,有些基本情况可能会失败,使用上述系统属性可以纠正遇到的问题,下面两个除外。
-------------------------------------------------------------------------------------------------
老版本中,PlainSocketImpl 中的 getInputStream() 和 getOutputStream() 方法返回的 InputStream
和 OutputStream 分别来自于其对应的扩展类型 FileInputStream 和 FileOutputStream,而这个在新版实现中则没有。
-------------------------------------------------------------------------------------------------
使用自定义或其它平台的 SocketImpl 的服务器套接字无法接受使用其他(自定义或其它平台)类型 SocketImpl 返回 Sockets 的连接。
-------------------------------------------------------------------------------------------------
通过这些更改,Java Socket API 将更易于维护,更好地维护将使套接字代码的可靠性得到改善。
同时 NIO 实现也可以在基础层面完成,从而保持 Socket 和 ServerSocket 类层面上的不变。
j.JDK14 - 改进 NullPointerExceptions 提示信息
a.介绍
通过 JVM 参数中添加-XX:+ShowCodeDetailsInExceptionMessages,
可以在空指针异常中获取更为详细的调用信息,更快的定位和解决问题。
a.b.c.i = 99; // 假设这段代码会发生空指针
b.Java 14 之前
Exception in thread "main" java.lang.NullPointerException
at NullPointerExample.main(NullPointerExample.java:5)
c.Java 14 之后
// 增加参数后提示的异常中很明确的告知了哪里为空导致
Exception in thread "main" java.lang.NullPointerException:
Cannot read field 'c' because 'a.b' is null.
at Prog.main(Prog.java:5)
k.JDK15 - DatagramSocket API重构
a.介绍
重新实现了老的 DatagramSocket API 接口,更改了 java.net.DatagramSocket 和 java.net.MulticastSocket
为更加简单、现代化的底层实现,更易于维护和调试。
b.示例
java.net.datagram.Socket和java.net.MulticastSocket的当前实现可以追溯到JDK 1.0,那时IPv6还在开发中。因此,当前的多播套接字实现尝试调和IPv4和IPv6难以维护的方式。
通过替换 java.net.datagram 的基础实现,重新实现旧版 DatagramSocket API。
更改java.net.DatagramSocket 和 java.net.MulticastSocket 为更加简单、现代化的底层实现。提高了 JDK 的可维护性和稳定性。
通过将java.net.datagram.Socket和java.net.MulticastSocket API的底层实现替换为更简单、更现代的实现来重新实现遗留的DatagramSocket API。
c.新的实现
易于调试和维护;
与Project Loom中正在探索的虚拟线程协同
l.JDK17 - 增强的伪随机数生成器
a.Random 类
// 随机一个int值
new Random().nextInt();
/**
* description 获取指定位数的随机数
*
* @param length 1
* @return java.lang.String
*/
public static String getRandomString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
b.ThreadLocalRandom 类
提供线程间独立的随机序列。它只有一个实例,多个线程用到这个实例,也会在线程内部各自更新状态。
它同时也是 Random 的子类,不过它几乎把所有 Random 的方法又实现了一遍。
---------------------------------------------------------------------------------------------
/**
* nextInt(bound) returns 0 <= value < bound; repeated calls produce at
* least two distinct results
*/
public void testNextIntBounded() {
// sample bound space across prime number increments
for (int bound = 2; bound < MAX_INT_BOUND; bound += 524959) {
int f = ThreadLocalRandom.current().nextInt(bound);
assertTrue(0 <= f && f < bound);
int i = 0;
int j;
while (i < NCALLS &&
(j = ThreadLocalRandom.current().nextInt(bound)) == f) {
assertTrue(0 <= j && j < bound);
++i;
}
assertTrue(i < NCALLS);
}
}
c.SplittableRandom 类 非线程安全,但可以 fork 的随机序列实现,适用于拆分子任务的场景。
/**
* Repeated calls to nextLong produce at least two distinct results
*/
public void testNextLong() {
SplittableRandom sr = new SplittableRandom();
long f = sr.nextLong();
int i = 0;
while (i < NCALLS && sr.nextLong() == f)
++i;
assertTrue(i < NCALLS);
}
m.JDK18- 默认字符集 UTF-8
a.介绍
JDK 终于将 UTF-8 设置为默认字符集。
在 Java 17 及更早版本中,默认字符集是在 Java 虚拟机运行时才确定的,取决于不同的操作系统、区域设置等因素,
因此存在潜在的风险。就比如说你在 Mac 上运行正常的一段打印文字到控制台的 Java 程序到了 Windows 上就会出现乱码,如果你不手动更改字符集的话。
n.JDK18 - 简易的 Web 服务器
a.介绍
Java 18 之后,你可以使用 jwebserver 命令启动一个简易的静态 Web 服务器。
-------------------------------------------------------------------------------------------------
jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and subdirectories on 127.0.0.1 port 8000
URL: http://127.0.0.1:8000/
-------------------------------------------------------------------------------------------------
这个服务器不支持 CGI 和 Servlet,只限于静态文件。
o.JDK21 序列化集合
a.介绍
JDK 21 引入了一种新的集合类型:Sequenced Collections(序列化集合,也叫有序集合) ,
这是一种具有确定出现顺序(encounter order)的集合(无论我们遍历这样的集合多少次,
元素的出现顺序始终是固定的)。序列化集合提供了处理集合的第一个和最后一个元素以及反向视图
(与原始集合相反的顺序)的简单方法。
b.Sequenced Collections 包括以下三个接口:
SequencedCollection
SequencedSet
SequencedMap
c.SequencedCollection 接口继承了 Collection接口, 提供了在集合两端访问、添加或删除元素以及获取集合的反向视图的方法。
interface SequencedCollection<E> extends Collection<E> {
// New Method
SequencedCollection<E> reversed();
// Promoted methods from Deque<E>
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
d.List 和 Deque 接口实现了SequencedCollection 接口。
这里以 ArrayList 为例,演示一下实际使用效果:
-------------------------------------------------------------------------------------------------
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1); // List contains: [1]
arrayList.addFirst(0); // List contains: [0, 1]
arrayList.addLast(2); // List contains: [0, 1, 2]
Integer firstElement = arrayList.getFirst(); // 0
Integer lastElement = arrayList.getLast(); // 2
List<Integer> reversed = arrayList.reversed();
System.out.println(reversed); // Prints [2, 1, 0]
e.SequencedSet接口直接继承了 SequencedCollection 接口并重写了 reversed() 方法。
interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
SequencedSet<E> reversed();
}
-------------------------------------------------------------------------------------------------
SortedSet 和 LinkedHashSet 实现了SequencedSet接口。
-------------------------------------------------------------------------------------------------
这里以 LinkedHashSet 为例,演示一下实际使用效果:
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>(List.of(1, 2, 3));
Integer firstElement = linkedHashSet.getFirst(); // 1
Integer lastElement = linkedHashSet.getLast(); // 3
linkedHashSet.addFirst(0); //List contains: [0, 1, 2, 3]
linkedHashSet.addLast(4); //List contains: [0, 1, 2, 3, 4]
System.out.println(linkedHashSet.reversed()); //Prints [5, 3, 2, 1, 0]
-------------------------------------------------------------------------------------------------
SequencedMap 接口继承了 Map接口, 提供了在集合两端访问、添加或删除键值对、获取包含 key 的 SequencedSet、
包含 value 的 SequencedCollection、包含 entry(键值对) 的 SequencedSet以及获取集合的反向视图的方法。
-------------------------------------------------------------------------------------------------
interface SequencedMap<K,V> extends Map<K,V> {
// New Methods
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// Promoted Methods from NavigableMap<K, V>
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
2 Reflect
2.1 反射
01.反射
a.定义
关键:【反射是在运行状态中】
【对于任意一个类,都能够知道这个类的所有属性和方法】
【对于任意一个对象,都能够调用它的任意一个属性和方法】
反射是Java提供的一种功能,通过反射可以无视Java的一些限制访问机制,直接使用某个类的私有变量或私有方法。
b.步骤
首先,通过对象得到它所对应的类型,
然后,通过Class类提供的一些方法得到对应的变量或者方法,
最后,再通过这些Field类和Method类直接访问某个对象的某个变量或者方法,而不是通过一般的通过.操作符进行访问
c.功能
在运行时,判断任意一个对象所属的类
在运行时,构造任意一个类的对象
在运行时,判断任意一个类所具有的成员变量和方法
在运行时,调用任意一个对象的方法
生成动态代理
02.反射
a.【类、对象与类对象】
在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例;
作为概念层次的类,其本身也具有某些共同的特性,如类名称、由类加载器去加载,都具有包,具有父类,属性和方法等;
于是,Java中有专门定义了一个类(Class类),去描述其他类所具有的这些特性,
因此,从此角度去看,类本身也都是属于Class类的对象。为与经常意义上的对象相区分,在此称之为”类对象”。
b.Object类与Class类
Object类和Class类没有直接的关系;
Object类是一切java类的父类,对于普通的java类,即便不声明,也是默认继承了Object类;
Class类是用于java反射机制的,一切java类,都有一个对应的Class对象。
c.java反射的作用
反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的Java 程序获取自身的信息,
并且可以操作类或对象的内部属性。 通过反射机制,可以在运行时访问Java 对象的属性,方法,构造方法等。
2.2 场景
01.场景
a.场景1:JDBC连接过程:
导包
获取Driver的实现类对象。
注册驱动。
提供需要连接的数据库信息。
创建Connection对象,获取连接。
b.场景2:反射改String
虽然String长度不可变,但是可以通过反射暴露修改String
String str = "hello";
//源码中的value数组
Field filed = String.class.getDeclaredField("value");
//设置可访问
filed.setAccessible(true);
//得到value数组
char[] chars = (char[])filed.get(str);
//修改
chars[0] = 'w';
System.out.println(str);
c.汇总
使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。
2.3 原理
01.说明
对于反射来说,操纵类最主要的方法是 invoke,所以搞懂了 invoke 方法的实现,也就搞定了反射的底层实现原理了
02.invoke 方法的执行流程如下
a.查找方法
当通过 java.lang.reflect.Method 对象调用 invoke 方法时,Java 虚拟机(JVM)首先确认该方法是否存在并可以访问
这包括检查方法的访问权限、方法签名是否匹配等
b.安全检查
如果方法是私有的或受保护的,还需要进行访问权限的安全检查。如果当前调用者没有足够的权限访问这个方法
将抛出 IllegalAccessException
c.参数转换和适配
invoke 方法接受一个对象实例和一组参数,需要将这些参数转换成对应方法签名所需要的类型
并且进行必要的类型检查和装箱拆箱操作
d.方法调用
对于非私有方法,Java 反射实际上是通过 JNI(Java Native Interface,Java 本地接口)调用到 JVM 内部的 native 方法
例如 java.lang.reflect.Method.invoke0()
这个 native 方法负责完成真正的动态方法调用。对于 Java 方法,JVM 会通过方法表、虚方法表(vtable)进行查找和调用
对于非虚方法或者静态方法,JVM 会直接调用相应的方法实现
e.异常处理
在执行方法的过程中,如果出现任何异常,JVM 会捕获并将异常包装成 InvocationTargetException 抛出
应用程序可以通过这个异常获取到原始异常信息
f.返回结果
如果方法正常执行完毕,invoke 方法会返回方法的执行结果,或者如果方法返回类型是 void,则不返回任何值
2.4 操作API
01.反射
a.反射入口:获取类的对象
Class.forName("全类名") --person对象
类名.class --person对象
对象名.getClass() --person对象
b.构造方法:无父类构造,无法继承
Constructor<?>[] getConstructors() --所有公共的构造方法
Constructor<?>[] getDeclaredConstructors() --所有公共、私有的构造方法
Constructor<T> getConstructor(类<?>... parameterTypes) --指定公共
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) --指定私有
c.属性
Field[] getFields() --所有公共的属性(父类、本类
Field[] getDeclaredFields() --所有公共、私有的属性(本类)
Field getField(String name) --指定公共
Field getDeclaredField(String name) --指定私有
d.方法
Method[] getMethods() --所有公共的方法(本类、父类、接口)
Method[] getDeclaredMethods() --所有公共、私有的方法(本类)
Method getMethod(String name, 类<?>... parameterTypes) --指定公共
Method getDeclaredMethod(String name, 类<?>... parameterTypes) --指定私有
e.父类
Class<? super T> getSuperclass() --父类只有一个,“单继承、多实现”
f.接口
Class<?>[] getInterfaces() --接口拥有多个,“单继承、多实现”
g.类对象 -> 对象实例
T newInstance() --创建由此类对象表示的类的新实例
02.反射:入口、构造方法、属性、方法、父类、接口
a.入口
Class.forName("全类名") Class<Person> person = Class.forName("org.myslayers.entity.Person");
类名.class Class<Person> person = Person.class;
对象名.getClass() Class<Person> person = new Person().getClass();
b.构造方法:无父类构造,无法继承
a.代码
public class Person {
public Person() {
}
private Person(String name) {
this.name = name;
}
public Person(Integer id) {
this.id = id;
}
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
public static void demo01() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
// 所有公共、私有的构造方法
Constructor<?>[] c1s = perClazz.getDeclaredConstructors();
for (Constructor c1 : c1s) {
System.out.println(c1);
}
// 所有公共的构造方法
Constructor<?>[] c2s = perClazz.getConstructors();
for (Constructor c2 : c2s) {
System.out.println(c2);
}
// 指定私有的构造方法
Constructor<?> c3 = perClazz.getDeclaredConstructor(String.class);
System.out.println(c3);
// 指定公共的构造方法
Constructor<?> c4 = perClazz.getConstructor(Integer.class);
System.out.println(c4);
}
b.结果
// 所有公共、私有的构造方法
public reflect.Person()
private reflect.Person(java.lang.String)
public reflect.Person(java.lang.Integer)
public reflect.Person(int, java.lang.String, int)
// 所有公共的构造方法
public reflect.Person()
public reflect.Person(java.lang.Integer)
public reflect.Person(int, java.lang.String, int)
// 指定私有的构造方法
private reflect.Person(java.lang.String)
// 指定公共的构造方法
public reflect.Person(java.lang.Integer)
c.属性
a.代码
public class Person {
private int id;
private String name;
private int age;
public String desc;
}
public static void demo02() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
// 所有公共的属性(父类、本类)
Field[] f1s = perClazz.getFields();
for (Field f1 : f1s) {
System.out.println(f1);
}
// 所有公共、私有的属性(本类)
Field[] f2s = perClazz.getDeclaredFields();
for (Field f2 : f2s) {
System.out.println(f2);
}
}
b.结果
// 所有公共的属性(父类、本类)
public java.lang.String reflect.Person.desc
// 所有公共、私有的属性(本类)
private int reflect.Person.id
private java.lang.String reflect.Person.name
private int reflect.Person.age
public java.lang.String reflect.Person.desc
d.方法
a.代码
public class Person implements MyInterface {
@Override
public void interfaceMethod() {
System.out.println("interface Method...");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public static void demo03() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
// 所有公共的方法(本类、父类、接口)
Method[] m1s = perClazz.getMethods();
for (Method m1 : m1s) {
System.out.println(m1);
}
// 所有公共、私有的方法(本类)
Method[] m2s = perClazz.getDeclaredMethods();
for (Method m2 : m2s) {
System.out.println(m2);
}
}
b.结果
// 所有公共的方法(本类、父类、接口)
public void reflect.Person.interfaceMethod() --接口
public int reflect.Person.getId() --本类
public void reflect.Person.setId(int) --本类
public final void java.lang.Object.wait() throws java.lang.InterruptedException --父类
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException --父类
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException--父类
public boolean java.lang.Object.equals(java.lang.Object) --父类
public java.lang.String java.lang.Object.toString() --父类
public native int java.lang.Object.hashCode() --父类
public final native java.lang.Class java.lang.Object.getClass() --父类
public final native void java.lang.Object.notify() --父类
public final native void java.lang.Object.notifyAll() --父类
// 所有公共、私有的方法(本类)
public int reflect.Person.getId() --本类
public void reflect.Person.setId(int) --本类
e.父类
a.代码
public class Person {
}
public static void demo05() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
// 父类只有一个,“单继承、多实现”
Class<?> superclass = perClazz.getSuperclass();
System.out.println(superclass);
}
b.结果
Class java.lang.Object
f.接口
a.代码
public class Person implements MyInterface, MyInterface2 {
@Override
public void interfaceMethod() {
System.out.println("interface Method...");
}
@Override
public void interface2Method() {
System.out.println("interface2 Method...");
}
}
public static void demo05() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
// 接口拥有多个,“单继承、多实现”
Class<?>[] interfaces = perClazz.getInterfaces();
for (Class<?> inter:interfaces){
System.out.println(inter);
}
}
b.结果
interface reflect.MyInterface
interface reflect.MyInterface2
03.反射:类对象 -> 对象实例、类对象构造方法 -> 对象实例
a.类对象 -> 创建实例
a.代码
public static void demo01() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
Object instance = perClazz.newInstance();
Person per = (Person) instance;
System.out.println(per);
}
b.结果
reflect Person@1b6d3586
b.类对象 -> 创建实例 -> 强转为Person类型,间接访问属性(setter、getter)
a.代码
public static void demo02() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
Person per = (Person) perClazz.newInstance();
per.setName("zs");
System.out.println(per.getName());
}
b.结果
zs
c.类对象 -> 创建实例 -> 强转为Person类型,直接访问属性(xxxFiled.set(per, 1))
a.代码
public static void demo03() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
Person per = (Person) perClazz.newInstance();
Field idFiled = perClazz.getDeclaredField("id");
idFiled.setAccessible(true); // private修饰的id,通过setAccessible(true)修改访问权限
idFiled.set(per,1); // per.setId(1);
System.out.println(per.getId());
}
b.结果
1
d.类对象 -> 创建实例 -> 操作方法:普通方法(public)、无参方法(private,修改权限)、有参方法(private,修改权限)
a.代码
public static void demo04() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
Person per = (Person) perClazz.newInstance();
// 普通方法(public)
per.interfaceMethod();
// 无参方法(private,修改权限)
Method method = perClazz.getDeclaredMethod("privateMethod", null);
method.setAccessible(true);
method.invoke(per, null);
// 有参方法(private,修改权限)
Method method2 = perClazz.getDeclaredMethod("privateMethod2", String.class);
method2.setAccessible(true);
method2.invoke(per, "zs");
}
b.结果
// 普通方法(public)
interface method...
// 无参方法(private,修改权限)
private method...
// 有参方法(private,修改权限)
private method...zs
e.类对象构造方法 -> 对象实例
a.代码
public static void demo05() throws Exception {
Class<?> perClazz = Class.forName("reflect.Person");
// 根据public无参构造方法获取实例
Constructor<?> c1 = perClazz.getConstructor();
Person p1 = (Person) c1.newInstance();
System.out.println(p1);
// 根据public有参构造方法获取实例
Constructor<?> c2 = perClazz.getConstructor(Integer.class);
Person p2 = (Person) c2.newInstance(12);
System.out.println(p2);
// 根据private有参构造方法获取实例(private,修改权限)
Constructor<?> c3 = perClazz.getDeclaredConstructor(String.class);
c3.setAccessible(true);
Person p3 = (Person) c3.newInstance("zs");
System.out.println(p3);
}
b.结果
// 根据public无参构造方法获取实例
reflect.Person@1b6d3586
// 根据public有参构造方法获取实例
reflect.Person@1b6d3586
// 根据private有参构造方法获取实例(private,修改权限)
reflect.Person@1b6d3586
04.反射:动态加载类名和方法、反射可以越过泛型检查、自定义set方法
a.动态加载类名和方法
a.代码
public static void demo01() throws Exception {
// 加载txt文件
Properties prop = new Properties();
prop.load(new FileReader("class.txt"));
// 加载类名
String classname = prop.getProperty("classname");
// 加载方法名
String methodname = prop.getProperty("methodname");
Class<?> perClazz = Class.forName(classname);
Method method = perClazz.getMethod(methodname);
method.invoke(perClazz.newInstance());
}
b.结果
static Method ...
b.反射可以越过泛型检查(虽然反射可以访问private等访问修饰符不允许访问的属性/方法,但忽略了泛型约束,易程序混乱)
a.代码
public static void demo02() throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(123);
list.add(3);
list.add(2);
Class<?> listClazz = list.getClass();
Method method = listClazz.getMethod("add", Object.class);
method.invoke(list, "zs..."); // 使用正确,对象名为原数组list
System.out.println(list);
}
b.结果
[123, 3, 2, zs...]
c.代码
public static void demo02() throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(123);
list.add(3);
list.add(2);
Class<?> listClazz = list.getClass();
Method method = listClazz.getMethod("add", Object.class);
method.invoke(listClazz.newInstance(), "zs..."); // 错误,listClazz.newInstance()与list两对象
System.out.println(list);
}
d.结果
[123, 3, 2]
c.自定义set方法
a.自定义PropertyUtil工具类
package reflect;
import java.lang.reflect.Field;
public class PropertyUtil {
public static void setProperty(Object obj,String propertyName,Object value) throws Exception{
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(propertyName);
field.setAccessible(true);
field.set(obj, value);
}
}
b.代码
public static void demo02() throws Exception {
// 给Person赋值
Person per = new Person();
PropertyUtil.setProperty(per, "name", "zs");
PropertyUtil.setProperty(per, "age", 23);
// 给Student赋值
Student stu = new Student();
PropertyUtil.setProperty(stu, "score", 98);
//打印
System.out.println(per.getName() + "," + per.getAge());
System.out.println(stu.getScore());
}
c.结果
zs,23
98
2.5 动态绑定
01.定义
在编译期间方法并不会和类绑定在一起
而是在程序运行的过程中,JVM需要根据具体的实例对像才能确定此时要调用的显围个方法
02.典型代表
多态、方法重写
03.方法重写、属性覆盖不一致
对于JVM而言,普通方法是在JVM【运行期】绑定的,而属性是在【编译期】绑定的,
2.6 动态代理:2种
01.定义
动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法
1.JDK动态代理,代理的是接口
2.CGLib动态代理,代理的是普通类
02.设计
动态代理类(基于接口实现)
静态代理是代理类在代码运行前已经创建好,并生成class文件
动态代理类是代理类在程序运行时创建的代理模式
动态代理类的代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的
03.Java中实现动态的方式
JDK中的动态代理
Java类库CGLib
使用Spring的Aop模块完成动态代理功能
04.应用场景
统计每个 api 的请求耗时
统一的日志输出
校验被调用的 api 是否已经登录和权限鉴定
Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程
2.7 动态代理:说明
01.什么是代理
a.定义
代理是一种设计模式,通过代理类代替委托类执行某些操作
代理类可以隐藏委托类的实现,并实现客户与委托类间的解耦
b.优点
可以隐藏委托类的实现
可以实现客户与委托类间的解耦,在不修改委托类代码的情况下做一些额外的处理
02.静态代理
a.定义
静态代理是在程序运行前就已经存在的代理方式,代理类通常在Java代码中定义
b.实现
a.接口定义
public interface Sell {
void sell();
void ad();
}
b.委托类
public class Vendor implements Sell {
public void sell() {
System.out.println("In sell method");
}
public void ad() {
System.out.println("ad method");
}
}
c.代理类
public class BusinessAgent implements Sell {
private Sell vendor;
public BusinessAgent(Sell vendor){
this.vendor = vendor;
}
public void sell() {
vendor.sell();
}
public void ad() {
vendor.ad();
}
}
c.优点
可以实现客户与委托类间的解耦,在不修改委托类代码的情况下做一些额外的处理
d.局限
运行前必须编写好代理类
03.动态代理
a.定义
动态代理是在程序运行时创建的代理方式,代理类不是在Java代码中定义的,而是在运行时动态生成的
b.优势
可以方便地对代理类的函数进行统一处理,而不用修改每个代理类的函数
c.实现
a.InvocationHandler接口
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
b.委托类
public class Vendor implements Sell {
public void sell() {
System.out.println("In sell method");
}
public void ad() {
System.out.println("ad method");
}
}
c.中介类
public class DynamicProxy implements InvocationHandler {
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(obj, args);
System.out.println("after");
return result;
}
}
d.动态生成代理类
public class Main {
public static void main(String[] args) {
DynamicProxy inter = new DynamicProxy(new Vendor());
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
sell.sell();
sell.ad();
}
}
d.原理
动态代理关系由两组静态代理关系组成
04.代理模式
a.特点
代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用
b.实现
a.接口
public interface AppService {
public boolean createApp(String name);
}
b.实现类
public class AppServiceImpl implements AppService {
public boolean createApp(String name) {
System.out.println("App["+name+"] has been created.");
return true;
}
}
c.日志处理器
public class LoggerInterceptor implements InvocationHandler {
private Object target;
public LoggerInterceptor(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");
Object result = method.invoke(target, arg);
System.out.println("Before return:"+result);
return result;
}
}
d.外部调用
public class Main {
public static void main(String[] args) {
AppService target = new AppServiceImpl();
AppService proxy = (AppService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new LoggerInterceptor(target));
proxy.createApp("Kevin Test");
}
}
2.8 静态代理、动态代理、cglib代理
00.汇总
a.静态代理
静态代理需要代理类和目标类实现同一接口
b.动态代理
动态代理是在运行时动态生成的,不需要事先目标对象的接口
c.cglib代理
cglib是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象
01.静态代理
a.定义
静态代理需要代理类和目标类实现同一接口
b.示例
a.接口定义
public interface IUser {
void sayName();
}
b.实现类
public class User implements IUser {
@Override
public void sayName() {
System.out.println("tntaxin");
}
}
c.代理类
public class UserProxy implements IUser {
private IUser target;
public UserProxy(IUser obj) {
this.target = obj;
}
@Override
public void sayName() {
System.out.println("我是它的代理");
this.target.sayName();
}
}
d.测试类
public class App {
public static void main(String[] args) {
IUser user = new User();
IUser userProxy = new UserProxy(user);
userProxy.sayName();
}
}
e.运行效果
输出:
我是它的代理
tntaxin
02.动态代理
a.定义
动态代理是在运行时动态生成的,不需要事先目标对象的接口
b.示例
a.动态代理类
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(),
this.target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是他的代理");
method.invoke(target, args);
return null;
}
});
}
}
b.测试类
public class App {
public static void main(String[] args) {
IUser user = new User();
ProxyFactory proxyFactory = new ProxyFactory(user);
IUser userProxy2 = (IUser) proxyFactory.getProxyInstance();
System.out.println(userProxy2.getClass());
userProxy2.sayName();
}
}
c.运行效果
输出:
class com.sun.proxy.$Proxy0
我是它的代理
tntaxin
d.生成代理类文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
03.cglib代理
a.定义
cglib是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象
b.示例
a.依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
b.代理工厂类
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
Enhancer en = new Enhancer();
en.setSuperclass(target.getClass());
en.setCallback(this);
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我是cglib生成的代理");
method.invoke(target, objects);
return null;
}
}
c.测试类
public class App {
public static void main(String[] args) {
IUser user = new User();
IUser userProxy3 = (IUser) new ProxyFactory(user).getProxyInstance();
System.out.println(userProxy3.getClass());
userProxy3.sayName();
}
}
d.运行效果
输出:
class org.example.UserEnhancerByCGLIBEnhancerByCGLIBEnhancerByCGLIB8458a7f7
我是cglib生成的代理
tntaxin
3 Stream
3.1 串行流
01.引入Stream
a.原因
它与java.io包里的InputStream和OutputStream是完全不同的概念,
Java8中的Stream是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作
b.题目:求一个集合中字符串长度小于5的数量
@Test
public void lenIter() {
List<String> list = Arrays.asList("java", "scala", "python", "shell", "ruby");
int num = 0;
for(String lan: list) {
if(lan.length() < 5) {
num++;
}
}
System.out.println(num);
}
c.题目:求一个集合中字符串长度小于5的数量
@Test
public void lenStream() {
List<String> list = Arrays.asList("java", "scala", "python", "shell", "ruby");
long num = list.parallelStream().filter(x -> x.length() < 5).count();
System.out.println(num);
}
02.什么是Stream
a.概念1
Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;
高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、
“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
b.概念2
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。
顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。
而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
Stream将要处理集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
3.2 并行流
01.说明
a.stream()
小规模数据的顺序读写操作
与性能密集相关的操作
b.parallelStream()
大规模数据的并行读操作或无副作用操作
涉及共享状态的写操作,或顺序敏感的操作
02.为什么parallelStream不适合写操作?
a.线程安全问题
并行流会在多个线程中同时处理流中的元素,如果写操作修改了共享的外部状态(如集合、文件等),就会引发线程安全问题
例如,多个线程同时向一个 ArrayList 添加数据,可能会导致 ConcurrentModificationException 或数据覆盖
并行流并不能保证线程安全性,因此,如果流中的元素是共享资源或操作本身不是线程安全的,你需要确保正确同步或使用线程安全的数据结构
b.副作用不可控
并行流的操作顺序是不保证的,因此写操作的顺序也无法预测
如果对顺序敏感的写操作依赖于流的顺序,会导致结果不正确
c.性能开销
即使使用线程安全的数据结构(如 ConcurrentHashMap 或 Collections.synchronizedList)
并行写操作也会因锁竞争而降低性能,甚至比串行操作更慢
并非所有的流操作都能从并行化中受益,有些操作(如短流操作或依赖于顺序的操作)并行执行反而可能导致性能下降
而且,过多的上下文切换也可能抵消并行带来的优势
d.底层实现
Stream.parallel()并行流默认使用的是ForkJoinPool.commonPool()作为线程池,该线程池默认最大线程数就是CPU核数
并行流默认使用的线程池大小可能与机器的实际物理核心数相适应,但也可能与其他并发任务争夺系统资源
e.数据分区
Java的并行流机制会自动对数据进行分区,但在某些情况下,数据分区的开销可能大于并行带来的收益,特别是对于小规模数据集
f.结果一致性
并行流并不保证执行的顺序性,也就是说,如果流操作的结果依赖于元素的处理顺序,则不应该使用并行流
g.事务处理
在涉及到事务操作时,通常需要避免在并行流中直接处理,如上述例子所示,应当将事务边界放在单独的服务方法内,确保每个线程内的事务独立完成
03.Stream为什么不支持指定线程池
a.说明1
简单总结就是官方考虑过相关方案,认为没必要,parallel 提供了简单直接的使用方式
官方的初衷就是流式编程需要轻松实现并行支持,而指定线程池会使API更复杂化,不便使用
还会有线程安全性问题。需要指出的是,官方考虑过相关方案
b.说明2
并行流默认使用公共线程池,基本思想为分治
公共池类型为 ForkJoinPool, 公共线程池并发度为CUP核数 - 1
适用于处理CPU密集型任务。任务为递归型任务,任务可以划分为子任务
空闲线程可以”窃取“待执行任务,充分利用线程池
一般情况下,使用公用池时,任务队列中会存在比较多的小任务
使用公用池的好处是可以避免创建过多无用的线程,特别是对于CPU密集型任务,新增线程反而会增加上下文开销
c.慎用并行流
无论如何不要在并行流里执行阻塞任务,除非你对于其内部实现非常了解,否则,你会遇到各种各样所谓的坑
如果你能够对以上描述有深入理解,比如 ManagedBlocker 的工作原理,那么可能没人可以拦住你使用阻塞任务
但是,由于公共池是公用的,每一次任务的成功执行不一定能保证整体执行多条任务时能够执行成功
这也是推荐使用自定义线程池的原因之一
3.3 操作API
01.创建流
Stream.of() 创建一个包含指定元素的流
Arrays.stream() 将数组转换为流,允许对数组中的元素进行流式操作
Collection.stream() 从集合中创建流,允许对集合中的元素进行流式操作
Stream.generate() 创建一个无限流,使用提供的生成器函数生成元素
Stream.iterate() 创建一个无限流,使用提供的种子值和迭代函数生成元素
02.中间操作
filter(Predicate<? super T> predicate) 过滤流中的元素,仅保留满足给定条件的元素
map(Function<? super T, ? extends R> mapper) 将流中的每个元素映射到一个新的元素,返回一个新的流
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 将流中的每个元素映射到一个流,并将所有流合并为一个流
distinct() 去重,返回一个只包含唯一元素的流
sorted() 对流中的元素进行自然排序,返回一个排序后的流
sorted(Comparator<? super T> comparator) 根据提供的比较器对流中的元素进行排序
peek(Consumer<? super T> action) 对流中的每个元素执行指定的操作,通常用于调试
limit(long maxSize) 返回一个包含最多指定数量元素的流
skip(long n) 跳过流中的前 n 个元素,返回剩余的流
03.终止操作
forEach(Consumer<? super T> action) 对流中的每个元素执行指定的操作
collect(Collector<? super T, A, R> collector) 将流中的元素收集到集合或其他形式的结果中
reduce(T identity, BinaryOperator<T> accumulator) 使用提供的累加器将流中的元素归约为单个值,带有初始值
reduce(BinaryOperator<T> accumulator) 使用提供的累加器将流中的元素归约为单个值,不带初始值
count() 返回流中元素的数量
min(Comparator<? super T> comparator) 返回流中最小的元素,根据提供的比较器进行比较
max(Comparator<? super T> comparator) 返回流中最大的元素,根据提供的比较器进行比较
anyMatch(Predicate<? super T> predicate) 检查流中是否有任何元素满足给定条件
allMatch(Predicate<? super T> predicate) 检查流中是否所有元素都满足给定条件
noneMatch(Predicate<? super T> predicate) 检查流中是否没有任何元素满足给定条件
findFirst() 返回流中的第一个元素(如果存在)
findAny() 返回流中的任意元素(如果存在)
toArray() 将流中的元素收集到数组中
toArray(IntFunction<A[]> generator) 将流中的元素收集到指定类型的数组中
04.其他操作
flatMapToInt(ToIntFunction<? super T> mapper) 将流中的元素映射到一个 IntStream
flatMapToDouble(ToDoubleFunction<? super T> mapper) 将流中的元素映射到一个 DoubleStream
flatMapToLong(ToLongFunction<? super T> mapper) 将流中的元素映射到一个 LongStream
mapToInt(ToIntFunction<? super T> mapper) 将流中的元素映射到一个 IntStream
mapToDouble(ToDoubleFunction<? super T> mapper) 将流中的元素映射到一个 DoubleStream
mapToLong(ToLongFunction<? super T> mapper) 将流中的元素映射到一个 LongStream
3.4 流操作1
00.汇总
a.Stream的创建
通过数组创建:java.util.Arrays.stream(T[] array)
通过集合创建:java.util.Collection.stream()
通过Stream的静态方法:of()、iterate()、generate()
使用BufferedReader.lines()方法:将每行内容转成流
使用Pattern.splitAsStream()方法:将字符串分隔成流
b.Stream的转换
filter操作:原stream中满足条件的元素构成新的stream
map操作:遍历原stream中的元素,转换后构成新的stream,有map、mapToInt、mapToDouble、mapToLong
distinct操作:distinct()
排序操作:sorted()
常用转换操作:flatMap()、limit()、skip()
c.reduction操作
reduce操作:reduce(BinaryOperator<T> accumulator)
reduce(T identity, BinaryOperator<T> accumulator)
reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
其他终端操作:count()
max(Comparator<? super T> comparator)
min(Comparator<? super T> comparator)
-----------------------------------------------------------------------------------------------------
主要用于将流中的元素组合成一个单一的结果
适用于简单的聚合操作,如求和、求积、求最大值等
d.collect操作
toList()
toSet()
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
joining(CharSequence delimiter)
groupingBy(Function<? super T, ? extends K> classifier)
partitioningBy(Predicate<? super T> predicate)
-----------------------------------------------------------------------------------------------------
用于将流中的元素累积到一个可变的结果容器中
适用于复杂的结果构建,如收集到集合、分组、分区等
01.Stream的创建
a.通过数组创建:java.util.Arrays.stream(T[] array)
public static void main(String[] args) {
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
}
b.通过集合创建:java.util.Collection.stream()
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 创建一个顺序流
Stream<String> parallelStream = list.parallelStream(); // 创建一个并行流
}
c.通过Stream的静态方法:of()、iterate()、generate()
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6); // of()
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4); // iterate()
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3); // generate()
stream3.forEach(System.out::println);
}
d.使用BufferedReader.lines()方法:将每行内容转成流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
e.使用Pattern.splitAsStream()方法:将字符串分隔成流
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
02.Stream的转换
a.filter操作:原stream中满足条件的元素构成新的stream
public static void main(String[] args) {
List<String> list = Arrays.asList("java", "scala", "python", "shell", "ruby");
long num = list.parallelStream().filter(x -> x.length() < 5).count();
System.out.println(num);
}
-----------------------------------------------------------------------------------------------------
2
b.map操作:遍历原stream中的元素,转换后构成新的stream
public static void main(String[] args) {
List<String> list = Arrays.asList(new String[]{"a", "b", "c"});
List<String> result = list.stream().map(String::toUpperCase).collect(Collectors.toList());
result.forEach(x -> System.out.print(x + " "));
}
-----------------------------------------------------------------------------------------------------
A B C
c.distinct操作
public static void main(String[] args) {
Stream<String> distinctStream = Stream.of("bj","shanghai","tianjin","bj","shanghai").distinct();
Stream<String> sortedStream = distinctStream.sorted(Comparator.comparing(String::length));
sortedStream.forEach(x -> System.out.print(x + " "));
}
-----------------------------------------------------------------------------------------------------
bj tianjin shanghai
d.排序操作
public static void main(String[] args) {
Stream<Integer> sortedStream = Stream.of(1,3,7,4,5,8,6,2).sorted();
sortedStream.collect(Collectors.toList()).forEach(x -> System.out.print(x + " "));
System.out.println();
Stream.of(1,3,7,4,5,8,6,2).sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
Stream<Integer> sortedReverseStreamV2 = Stream.of(1,3,7,4,5,8,6,2)
.sorted((Integer o1, Integer o2) -> o2 - o1);
sortedReverseStreamV2.collect(Collectors.toList()).forEach(x -> System.out.print(x + " "));
}
-----------------------------------------------------------------------------------------------------
1 2 3 4 5 6 7 8
8 7 6 5 4 3 2 1
03.reduction操作
a.reduction操作
reduction就是从stream中取出结果,是terminal operation,因此经过reduction后的stream不能再使用了。
主要包含以下操作: findFirst()/findAny()/allMatch/anyMatch()/noneMatch等等
-----------------------------------------------------------------------------------------------------
reduce() 是一个聚合操作,它将流中的元素按某种方式结合起来,最终生成一个单一的结果。
通常用来执行像求和、求积、最大值、最小值等操作。
-----------------------------------------------------------------------------------------------------
public static void main(String[] args) {
Stream<String> wordList = Stream.of("bj", "tj", "sh", "yy", "yq").distinct();
Optional<String> firstWord = wordList.filter(word -> word.startsWith("y")).findFirst();
System.out.println(firstWord.orElse("unknown"));
}
-----------------------------------------------------------------------------------------------------
yy
b.reduce方法
public static void main(String[] args) {
Stream<Integer> list = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> result = list.reduce((x, y) -> x + y);
System.out.println(result);
}
-----------------------------------------------------------------------------------------------------
Optional[15]
04.collect操作
a.介绍
collect()方法可以对stream中的元素进行各种处理后,得到stream中元素的值。
并且Collectors接口提供了很方便的创建Collector对象的工厂方法。
-----------------------------------------------------------------------------------------------------
collect() 是一个用于收集流中元素的操作,通常将流的元素转换成其他形式(如集合、映射等)。
它是一个非常灵活的操作,通常与 Collector 接口配合使用。
b.代码
public static void main(String[] args) {
List<String> list = Stream.of("hello", "world", "hello", "java")
.collect(Collectors.toList());
list.forEach(x -> System.out.print(x + " "));
System.out.println();
Set<String> set = Stream.of("hello", "world", "hello", "java")
.collect(Collectors.toSet());
set.forEach(x -> System.out.print(x + " "));
System.out.println();
Set<String> treeSet = Stream.of("hello", "world", "hello", "java")
.collect(Collectors.toCollection(TreeSet::new));
treeSet.forEach(x -> System.out.print(x + " "));
System.out.println();
String resultStr = Stream.of("hello", "world", "hello", "java")
.collect(Collectors.joining(","));
System.out.println(resultStr);
}
-----------------------------------------------------------------------------------------------------
hello world hello java
world java hello
hello java world
hello,world,hello,java
3.5 流操作2
01.常规操作
// 取出某一列
List<String> nameList = list.stream().map(m -> m.get("name")).collect(Collectors.toList());
TreeSet<String> allTypes = list.stream().map(record -> record.get("TYPE")).collect(Collectors.toCollection(TreeSet::new));
// dcjz该列包含name,返回List
List<Map> dcjzMatchs = list.stream().filter(item -> ObjectUtils.toString(item.get("dcjz")).contains(name)).collect(Collectors.toList());
List<Map<String, String>> typeList = list.stream().filter(record -> record.get("TYPE").equals(type)).collect(Collectors.toList());
// 根据type进行分组
Map<String, List<Map<String, Object>>> groupedByType = dataList.stream().collect(Collectors.groupingBy(row -> (String) row.get("type")));
List<List<Map<String, Object>>> resultList = new ArrayList<>(groupedByType.values());
// 取出pid等于2的Map
Map<String, String> result = list.stream().filter(map -> "2".equals(map.get("pid"))).findFirst().orElse(null);
// 是否全部元素都为无
boolean allNone = jzList.stream().allMatch(e -> e.equals("无"));
// 是否存在元素为无
boolean allNone = jzList.stream().anyMatch(e -> e.equals("无"));
// 使用forEach()方法打印每个元素
names.stream().forEach(name -> System.out.println(name));
02.数字运算
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
// 使用sorted()方法对整数流进行排序
numbers.stream().sorted().forEach(System.out::println);
// distinct()去除重复元素
numbers.stream().distinct().forEach(System.out::println);
// limit()截取前几个元素
System.out.println("\n截取前三个元素:");
numbers.stream().limit(3).forEach(System.out::println);
// max()找到最大值
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
// min()找到最小值
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
03.Collectors.toMap
a.定义
在 Java 中,Collectors.toMap() 是一个收集器,用于将流中的元素收集到一个 Map 中。
它通常用于从流中提取键和值,并将其存储在一个 Map 集合中。toMap 是一个静态方法,定义在 Collectors 类中。
b.基本形式
Collectors.toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper)
keyMapper:用于提取流中元素的键的函数。
valueMapper:用于提取流中元素的值的函数。
c.示例
Map<String, YzjDepartmentTreeVo> departmentMap = allDepartment.stream()
.map(department -> new YzjDepartmentTreeVo(department))
.collect(Collectors.toMap(Department::getId, treeVo -> treeVo));
-----------------------------------------------------------------------------------------------------
allDepartment.stream():将 allDepartment 列表转化为一个流。
.map(department -> new YzjDepartmentTreeVo(department)):对于流中的每个 department 元素,使用构造函数 YzjDepartmentTreeVo(department) 创建一个新的 YzjDepartmentTreeVo 对象。
.collect(Collectors.toMap(Department::getId, treeVo -> treeVo)):使用 Collectors.toMap 收集流中的元素,生成一个 Map。
Department::getId:这是用于提取每个 YzjDepartmentTreeVo 对象的键的函数。假设 department 对象有一个 getId 方法,它返回一个 String 类型的部门 ID。
treeVo -> treeVo:这是用于提取值的函数,在这个例子中,值就是 YzjDepartmentTreeVo 对象本身。
04.peek、map、flatMap
a.peek
a.概念
peek 方法是一个调试工具,它可以对流中的每个元素进行“窥视”,
但是它并不改变流的元素。通常用来在流的操作链中打印中间状态,或者用于调试目的。
b.示例
假设我们有一个列表,想要打印每个元素的值,然后继续流的处理:
-------------------------------------------------------------------------------------------------
import java.util.List;
public class StreamPeekExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// 使用 peek 打印流中的元素
numbers.stream()
.peek(n -> System.out.println("Before mapping: " + n)) // 打印原始值
.map(n -> n * 2) // 将元素乘以 2
.peek(n -> System.out.println("After mapping: " + n)) // 打印变换后的值
.collect(Collectors.toList());
}
}
b.map
a.概念
map 方法是用于将流中的元素映射成其他类型的元素,常用于对流中的每个元素应用一个函数,从而生成一个新的流。
b.示例
假设我们有一个整数列表,我们想要将每个整数都乘以 2
-------------------------------------------------------------------------------------------------
import java.util.List;
import java.util.stream.Collectors;
public class StreamMapExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// 使用 map 将每个整数乘以 2
List<Integer> doubledNumbers = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(doubledNumbers); // 输出 [2, 4, 6, 8, 10]
}
}
c.flatMap
a.概念
flatMap 方法用于将流中的元素“扁平化”,它通常用于将流中的每个元素映射成一个流,然后将这些流合并成一个大的流。
例如,当每个元素是一个集合或数组时,flatMap 会将所有这些集合的元素平展到一个单一的流中。
b.示例1
假设我们有一个包含字符串列表的列表,每个字符串列表可能包含多个单词,我们希望将所有的单词平展成一个单一的流。
-------------------------------------------------------------------------------------------------
import java.util.List;
import java.util.stream.Collectors;
public class StreamFlatMapExample {
public static void main(String[] args) {
List<List<String>> listOfLists = List.of(
List.of("apple", "banana", "cherry"),
List.of("date", "elderberry"),
List.of("fig", "grape")
);
// 使用 flatMap 将所有内部的 List 扁平化为一个单一的流
List<String> allFruits = listOfLists.stream()
.flatMap(List::stream) // 将每个内部列表的元素扁平化
.collect(Collectors.toList());
System.out.println(allFruits); // 输出 [apple, banana, cherry, date, elderberry, fig, grape]
}
}
c.示例2
假设我们有一个班级的学生数据,每个学生有一门或多门选修课。
假设有多个学生,每个学生有多门选修课。我们希望通过 flatMap 来平展这些课程,最终得到一个包含所有课程的单一流。
-------------------------------------------------------------------------------------------------
import java.util.List;
class Student {
private String name;
private List<String> courses;
public Student(String name, List<String> courses) {
this.name = name;
this.courses = courses;
}
public List<String> getCourses() {
return courses;
}
@Override
public String toString() {
return name + ": " + courses;
}
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapExample {
public static void main(String[] args) {
// 创建学生对象,学生每个人选修的课程列表
List<Student> students = Arrays.asList(
new Student("Alice", Arrays.asList("Math", "Physics")),
new Student("Bob", Arrays.asList("Chemistry", "Math")),
new Student("Charlie", Arrays.asList("History", "Biology", "Math"))
);
// 使用 flatMap 将每个学生的课程平展到一个单一的流
List<String> allCourses = students.stream()
.flatMap(student -> student.getCourses().stream()) // flatMap 将每个学生的课程列表扁平化为单一流
.distinct() // 去重,避免课程重复
.collect(Collectors.toList()); // 将结果收集到列表中
// 输出所有课程
System.out.println("All unique courses: " + allCourses);
}
}
-------------------------------------------------------------------------------------------------
students.stream():从学生列表创建一个流。
.flatMap(student -> student.getCourses().stream()):对每个学生,我们使用 getCourses() 方法获取他们的选修课列表,然后用 stream() 将这些列表转换成流,并将所有流扁平化为一个大的流。这个操作的关键是 flatMap,它将每个学生的课程列表 "平展"(扁平化)成一个流。
.distinct():用来去重,防止有重复的课程。
.collect(Collectors.toList()):最终将结果收集成一个列表。
-------------------------------------------------------------------------------------------------
flatMap 的作用就是将每个学生的课程列表扁平化,把每个列表内的课程从各自的流中提取出来,合并成一个大的流,这样我们就能方便地对所有课程进行去重、排序等操作。
如果使用 map,则每个学生会映射成一个流,最后得到的结果将是多个流嵌套,而不是一个平展的流。
-------------------------------------------------------------------------------------------------
对比 map 和 flatMap:
假设我们使用 map 来处理这个问题:
List<List<String>> allCoursesList = students.stream()
.map(student -> student.getCourses()) // map 会生成流的列表
.collect(Collectors.toList());
这时 allCoursesList 将是一个 List<List<String>>,而不是一个 List<String>。如果想要得到一个扁平化的结果,必须再调用 flatMap。
-------------------------------------------------------------------------------------------------
flatMap 是用来将流中的每个元素“扁平化”,例如将列表或集合映射成一个大的单一流。
适用于处理嵌套数据结构,比如每个元素是一个集合、数组或者其他流类型时,使用 flatMap 将其平展成一个单一的流。
3.6 Stream、Collection
00.汇总
a.存储vs计算
Collection用于存储数据
Stream用于计算数据
b.可变vs不可变
Collection中的元素可以被修改
Stream是不可变的,操作不会改变数据源
c.操作方式
Collection提供基本的增删改查操作
Stream提供声明性的数据处理操作
d.执行方式
Collection的操作是立即执行的
Stream的操作是惰性执行的,只有在终端操作时才会执行
e.并行处理
Stream支持并行处理,可以通过parallelStream()方法轻松实现并行操作
而Collection不直接支持并行处理
01.Collection
a.定义
Collection是Java集合框架的根接口之一,用于存储一组元素,常见的子接口包括List、Set和Queue
b.特点
数据结构:Collection是一个数据结构,用于存储和管理元素
存储元素:Collection接口及其实现类用于存储和操作一组元素
可变性:集合中的元素可以被添加、删除和修改
遍历:可以通过迭代器或增强型for循环遍历集合中的元素
c.用途
用于存储和管理数据,提供基本的增删改查操作
适用于需要频繁修改数据的场景
02.Stream
a.定义
Stream是Java8引入的一个新特性,表示元素的有序序列。它提供了一种声明性处理数据的方式
b.特点
无存储:Stream不存储数据,而是从Collection、数组或I/O通道中获取数据
惰性求值:Stream的操作是惰性执行的,只有在需要结果时才会执行
不可变性:Stream本身是不可变的,操作Stream不会修改数据源
函数式编程:支持函数式编程风格,提供了丰富的中间操作(如filter、map)和终端操作(如forEach、collect)
c.用途
用于对数据进行复杂的操作和转换,如过滤、映射、排序、聚合等
适用于需要对数据进行批处理和并行处理的场景
3.7 常见问题
00.汇总
Stream实现去重、平方根与收集
解决CollectToMap的Key重复问题
懒执行策略:map、peek
可能不被调用:map、peek
01.Stream实现去重、平方根与收集
a.需求
对一个包含重复元素的集合进行去重,计算每个元素的平方根,最终收集结果
b.代码
List<Double> numbers = Arrays.asList(4.0, 9.0, 4.0, 16.0, 25.0, 16.0);
List<Double> result = numbers.stream()
.filter(n -> n >= 0) // 过滤负数(避免平方根异常)
.distinct() // 去重
.map(Math::sqrt) // 计算平方根
.collect(Collectors.toList());
System.out.println(result); // 输出:[2.0, 3.0, 4.0, 5.0]
c.关键点
distinct() 依赖元素的 equals() 方法,需确保对象正确重写该方法
若处理的是自定义对象,可通过 map() 提取唯一标识去重
02.解决CollectToMap的Key重复问题
a.问题复现
a.说明
直接使用 Collectors.toMap() 时,若 Key 重复会抛出 IllegalStateException
b.代码
List<String> list = Arrays.asList("apple", "banana", "apple");
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(
s -> s, // Key
s -> s.length() // Value
)); // 抛出异常:Duplicate key "apple"
b.解决方案:
a.说明
通过第三个参数 mergeFunction 指定冲突处理策略
b.覆盖旧值:保留最后一次出现的 Value
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(
s -> s,
s -> s.length(),
(oldVal, newVal) -> newVal // 选择新值
));
c.保留旧值:保留第一次出现的 Value
(oldVal, newVal) -> oldVal
d.合并为集合:将相同 Key 的 Value 存入列表
Map<String, List<Integer>> map = list.stream()
.collect(Collectors.toMap(
s -> s,
s -> new ArrayList<>(List.of(s.length())),
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
));
03.懒执行策略:map、peek
a.说明
之所以有流操作,是因为有时候处理的数据比较多,无法一次性加载到内存中
为了优化stream的链式调用效率,stream还提供了一个懒加载策略
b.懒加载
懒加载也叫intermediate operation, 在stream的方法中,大部分都是懒加载,另外部分则是terminal operation
例如collect、count等,当有这种非懒加载的方法调用时,整个链式都会被执行,如开始的baseUse示例
c.举例
但peek和map,都是懒加载方法,即intermediate operation
d.peek
public static void peekLazy() {
Stream.of(1,2,3)
.peek(e -> System.out.println("peek lazy: " + e));
}
-----------------------------------------------------------------------------------------------------
执行之后,结果什么都没输出,表示peek中的逻辑没有被调用这里就是很大的一个坑,使用的时候要注意
e.map
public static void mapLazy() {
Stream.of(1,2,3)
.map(e -> {
e = e+1;
System.out.println("map lazy: " + e);
return e;
});
}
-----------------------------------------------------------------------------------------------------
执行之后,结果什么都没输出,表示peek中的逻辑没有被调用这里就是很大的一个坑,使用的时候要注意。
04.可能不被调用:map、peek
a.源码
This method exists mainly to support debugging, where you want
to see the elements as they flow past a certain point in a pipeline.
peek推荐只在debug模式中使用,方便查看元素的一些信息
b.示例
public static void peekNotInvoke() {
Stream.of(1,2,3)
.peek(e -> System.out.println("peek invoke: " + e))
.count(Collectors.toList());
System.out.println("peekNotInvoke");
}
c.说明
这里只输出了3, peek中的内容并没有输出,这个结果和你预期的一致不?
看下这段源码,明确提示了,使用peek时,有可能会被优化掉(即不调用)
d.count改为collect则可以执行
Stream.of(1,2,3)
.peek(e -> System.out.println("peek invoke: " + e))
.collect(Collectors.toList());
System.out.println("peekInvoke");
e.说明
我们在使用peek的时候,一定要注意peek方法是否会被优化。要不然就会成为一个隐藏很深的bug
同样的,map也有这个问题
3.8 List.of()
01.概况
a.介绍
List.of() 是Java 9中引入的静态工厂方法,用于创建不可修改的列表
b.示例
List<String> fruitsList = List.of("苹果", "香蕉", "樱桃");
c.关键特性
不可修改列表:列表无法被修改,任何修改操作都会导致异常
不由数组支持:它是一个独立的列表,修改不会影响任何底层数组
不允许空元素:插入 null 会抛出 NullPointerException
d.潜在问题
fruitsList.add("枣"); // 抛出 UnsupportedOperationException
fruitsList.set(0, "杏"); // 抛出 UnsupportedOperationException
List<String> fruitsList = List.of("苹果", null, "樱桃"); // 抛出 NullPointerException
02.实用示例
a.修改元素
a.Arrays.asList()
String[] numbersArray = {"一", "二", "三"};
List<String> numbersList = Arrays.asList(numbersArray);
numbersList.set(1, "二又二分之一");
System.out.println(Arrays.toString(numbersArray));
// 输出: [一, 二又二分之一, 三]
b.List.of()
List<String> numbersList = List.of("一", "二", "三");
numbersList.set(1, "二又二分之一"); // 抛出 UnsupportedOperationException
b.添加或删除元素
a.Arrays.asList()
List<String> colorsList = Arrays.asList("红色", "绿色", "蓝色");
colorsList.add("黄色"); // 抛出 UnsupportedOperationException
b.List.of()
List<String> colorsList = List.of("红色", "绿色", "蓝色");
colorsList.remove("绿色"); // 抛出 UnsupportedOperationException
c.处理空值
a.Arrays.asList()
List<String> petsList = Arrays.asList("狗", null, "猫");
System.out.println(petsList); // 输出: [狗, null, 猫]
b.List.of()
List<String> petsList = List.of("狗", null, "猫"); // 抛出 NullPointerException
03.应用场景
a.Arrays.asList()
你需要一个由数组支持的固定大小列表
你可能需要修改现有元素
你需要在列表中包含 null 元素
你希望列表中的更改反映到原始数组中
b.List.of()
你需要一个不可修改的列表
你想防止对列表的修改
你不需要 null 元素
你处理的是不可修改的数据集
3.9 Arrays.asList()
00.总结
a.Arrays.asList()设计模式
适配器模式,适配器模式简单来说就是不修改原对象,为了适应新的需求,适配成另一种接口或者类
b.Arrays.asList()得到的List可以修改吗?
答案:不可以
按理来说,List都是可以增改,但是这里却不可以,如果使用add方法,会报如下错误:
看了代码就很明显了,这个静态内部类ArrayList并不是我们常用的,而是自己定义的,而其中的数组用final修饰,表示不可修改。
因为大家使用数组的时候,都知道访问和使用不如list方便,所以适配为List一般都是为了更好的访问和传参,如果是真的是为了使用list增删(刷题大家常遇到),可以在外面再套一个new ArrayList<>()。
c.基本类型使用Arrays.asList()
当使用基本类型作为数组传入的时候,数组明明有多个元素,但是转换过来变成了数组类型的List,并且数组大小也是1,
这里可以看到传入的参数是可变参数的范型T,而范型无法接受基本类型,最终把数组本身作为对象,传入构建新的类型,
所以如果要直接转换成对应大小一样的List时,需要使用包装类型,这样就不会出现之前的问题。
01.坑1:不能直接使用 Arrays.asList() 来转换基本类型数组
a.问题描述
将基本类型数组(例如 int[])转换为 List 时,Arrays.asList() 方法返回的 List 包含的是一个单一的数组对象,而不是期望的元素列表。
b.示例代码
int[] numbers = {1, 2, 3};
List<int[]> list = Arrays.asList(numbers);
System.out.println(list.size()); // 输出 1
System.out.println(list.get(0)); // 输出 [I@someHashcode
c.原因
Arrays.asList() 方法的参数是泛型 T 类型,因此传入的 int[] 被当作单一对象,而不是一个 int 元素数组。
d.解决方案
a.将数组声明为包装类型数组:
Integer[] numbers = {1, 2, 3};
List<Integer> list = Arrays.asList(numbers);
b.使用 Java 8 及以上版本的 Arrays.stream() 方法:
int[] numbers = {1, 2, 3};
List<Integer> list = Arrays.stream(numbers).boxed().collect(Collectors.toList());
02.坑2:Arrays.asList() 返回的 List 不支持增删操作
a.问题描述
使用 Arrays.asList() 方法转换的 List 不支持 add() 和 remove() 操作,会抛出 UnsupportedOperationException 异常
b.示例代码
String[] strings = {"1", "2", "3"};
List<String> list = Arrays.asList(strings);
list.add("4"); // 抛出 UnsupportedOperationException
c.原因
Arrays.asList() 返回的 List 实际上是 Arrays 的内部类 ArrayList,
该类继承自 AbstractList,没有覆写父类的 add() 方法,默认实现为抛出异常。
d.解决方案
a.重新创建一个真正的 ArrayList:
String[] strings = {"1", "2", "3"};
List<String> list = new ArrayList<>(Arrays.asList(strings));
list.add("4"); // 操作成功
03.坑3:对原始数组的修改会影响 Arrays.asList() 返回的 List
a.问题描述
修改原始数组会同步修改通过 Arrays.asList() 获得的 List。
b.示例代码
String[] strings = {"1", "2", "3"};
List<String> list = Arrays.asList(strings);
strings[0] = "0";
System.out.println(list.get(0)); // 输出 "0"
c.原因
Arrays.asList() 返回的 List 直接使用了原始数组,因此两者共享同一个数据存储。
d.解决方案
a.创建一个新的 ArrayList 以实现数据解耦:
String[] strings = {"1", "2", "3"};
List<String> list = new ArrayList<>(Arrays.asList(strings));
strings[0] = "0";
System.out.println(list.get(0)); // 输出 "1"
3.10 stream().toList()
00.总结
使用toList()生成的List是一个不可改变的数组,即新增和移除都会报错
01.下面这两行代码相同吗?
a.代码
List<Integer> list1 = list.stream().toList();
List<Integer> list2 = list.stream().collect(Collectors.toList());
b.说明
在Idea里,Idea还会提醒你可以替换,难道真的是相同的api吗?
02.直接打印一下它们的Class
a.代码
List<Integer> list1 = list.stream().toList();
List<Integer> list2 = list.stream().collect(Collectors.toList());
System.out.println(list1.getClass());
System.out.println(list2.getClass());
b.结果
class java.util.ImmutableCollections$ListN
class java.util.ArrayList
c.说明
发现一个是ImmutableCollection,一个是ArrayList
从名字中就可以看出来list1是不可变的,remove一下果然抛出了异常
// all mutating methods throw UnsupportedOperationException
@Override public void add(int index, E element) { throw uoe(); }
@Override public boolean addAll(int index, Collection<? extends E> c) { throw uoe(); }
@Override public E remove(int index) { throw uoe(); }
@Override public void replaceAll(UnaryOperator<E> operator) { throw uoe(); }
@Override public E set(int index, E element) { throw uoe(); }
@Override public void sort(Comparator<? super E> c) { throw uoe(); }
3.11 Collectors.toMap()
00.总结
a.说明
HashMap和ConcurrentHashMap都重写了这个merge()方法
google了很多文章,也没有找到官方一点的 value判空的说明
b.继续揣测
HashMap的 get 方法,就算 key 不存在,拿到的 value 也会是 null
那么如果允许 value 为 null 存储,多个相同的 key ,其 value 都为 null
在使用的时候,某些场景下,跟直接不存入HashMap的结果是一样的
c.普通场景
一般我们使用 Hash 表,大多数情况是为了使用 get(key) 方法,避免 for 循环,if 判断key 执行业务
那么这种场景下,将 value 值为 null 的 key 存进来,就是单纯的浪费空间了
d.最终使用
报错:value不能为null Map<Integer, String> nameMap = list.stream().collect(Collectors.toMap(User::getId, User::getName));
推荐:如果是keys Set<Integer> ids = list.stream().map(User::getId).collect(Collectors.toSet());
推荐:如果是values List<String> nameList = list.stream().map(User::getName).collect(Collectors.toList());
这二者都比先转map,再拿keys或者values性能好。且这种非空的value 也能兼容其他map,就算是强转类型,也能使用其merge方法
01.示例
a.代码
List<User> list = new ArrayList<>();
list.add(new User(1, "a"));
list.add(new User(1, "b"));
list.add(new User(2, "c"));
list.add(new User(2, null));
@Data
class User{
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
b.过滤 + 筛选属性:Collectors.toList()
List<String> nameList = list.stream()
.filter(item -> StringUtils.hasText(item.getName()))
.collect(Collectors.toList());
c.而当你需要将List转为Map的时候,直接Collectors.toMap()
Map<Integer, String> nameMap = list.stream().collect(Collectors.toMap(User::getId, User::getName));
d.结果
报错,IllegalStateException
02.源码
a.报错
java.lang.IllegalStateException: Duplicate key a key重复了
b.点进toMap()方法
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
c.这里调用了另外一个方法,传了一个 throwingMerger() 参数
/**
返回一个合并函数,适合在 Map. merge() 中使用 或 toMap(),该函数总是抛出 IllegalStateException。这可用于强制执行所收集的元素是不同的假设。
返回:
一个总是抛出的合并函数 IllegalStateException
*/
private static <T> BinaryOperator<T> throwingMerger() {
return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
d.这时候你又发现(google,baidu),刚好有另外一个方法可以解决这个重复key的问题?手动提供key重复解决策略
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
e.于是你修改了下
Map<Integer, String> nameMap = list.stream().collect(Collectors.toMap(User::getId, User::getName, (k1, k2) -> k1));
f.结果
报错,NullPointerException空指针
03.打两个断点
a.断点进入这个merge方法
HashMap 不是支持null的value值吗?
b.处理一下value为null
List<User> list = new ArrayList<>();
list.add(new User(1, "a"));
list.add(new User(1, "b"));
list.add(new User(2, "c"));
list.add(new User(2, "d"));
@Data
class User{
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
c.写法1:stream
Map<Integer, String> nameMap = list.stream()
.filter(user -> StringUtils.hasText(user.getName()))
.collect(Collectors.toMap(User::getId, User::getName, (k1, k2) -> k1));
d.写法2:for + if
Map<Integer, String> nameMap = new HashMap<>();
list.forEach(user -> nameMap.put(user.getId(), user.getName()));
// 亦或者
for (User user : list) {
nameMap.put(user.getId(), user.getName());
}
4 Lambda
4.1 定义
01.表达式
a.语法
( parameter-list ) -> { expression-or-statements }
b.说明
() 中的 parameter-list 是以逗号分隔的参数。你可以指定参数的类型,也可以不指定(编译器会根据上下文进行推断)。Java 11 后,还可以使用 var 关键字作为参数类型,有点 JavaScript 的味道。
-> 相当于 Lambda 的标识符,就好像见到圣旨就见到了皇上。
{} 中的 expression-or-statements 为 Lambda 的主体,可以是一行语句,也可以多行。
02.用法
a.为变量赋值,示例如下:
Runnable r = () -> { System.out.println("沉默王二"); };
r.run();
b.作为 return 结果,示例如下:
static FileFilter getFilter(String ext)
{
return (pathname) -> pathname.toString().endsWith(ext);
}
c.作为数组元素,示例如下:
final PathMatcher matchers[] =
{
(path) -> path.toString().endsWith("txt"),
(path) -> path.toString().endsWith("java")
};
d.作为普通方法或者构造方法的参数,示例如下:
new Thread(() -> System.out.println("沉默王二")).start();
e.需要注意 Lambda 表达式的作用域范围。
public static void main(String[] args) {
int limit = 10;
Runnable r = () -> {
int limit = 5;
for (int i = 0; i < limit; i++)
System.out.println(i);
};
}
-----------------------------------------------------------------------------------------------------
上面这段代码在编译的时候会提示错误:变量 limit 已经定义过了。
和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过:
Lambda 表达式中使用的变量必须是 final 的。
4.2 接口:4个
01.函数式接口
a.如何自定义一个函数接口
标注@Functionallnterface,有且只有一个抽象方法。
b.注解
@Override:检查“重写父类或实现接口的方法”的正确性 可以不添加
@FunctionalInterface:检查“避免在函数式接口中,意外添加其他的抽象方法”的正确性 可以不添加
02.函数式接口:4个
a.汇总
java.util.function.Consumer消费者、Supplier供应者、Function函数、predicate断言
b.说明
Consumer(消费者)
功能:接受一个输入参数并对其进行操作,但不返回结果。常用于执行某些操作而不需要返回值的场景。
示例:Consumer<String> printer = s -> System.out.println(s);
-----------------------------------------------------------------------------------------------------
Supplier(供应者)
功能:不接受任何参数,但返回一个结果。常用于提供或生成数据的场景。
示例:Supplier<Double> randomValue = () -> Math.random();
-----------------------------------------------------------------------------------------------------
Function(函数)
功能:接受一个输入参数并返回一个结果。常用于将输入转换为输出的场景。
示例:Function<Integer, String> intToString = i -> Integer.toString(i);
-----------------------------------------------------------------------------------------------------
Predicate(断言)
功能:接受一个输入参数并返回一个布尔值。常用于条件判断和过滤的场景。
示例:Predicate<String> isEmpty = s -> s.isEmpty();
03.Consumer(消费者)
a.说明
有参,无返回值(消费型) Consumer<T>
b.示例
public class use_Consumer_FormattorName {
public static void formattorPersonMsg(Consumer<String[]> con1, Consumer<String[]> con2) {
// con1.accept(new String[]{ "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" });
// con2.accept(new String[]{ "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" });
// 一句代码搞定
con1.andThen(con2).accept(new String[] { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" });
}
public static void main(String[] args) {
formattorPersonMsg((s1) -> {
for (int i = 0; i < s1.length; i++) {
System.out.print(s1[i].split("\\,")[0] + " ");
}
}, (s2) -> {
System.out.println();
for (int i = 0; i < s2.length; i++) {
System.out.print(s2[i].split("\\,")[1] + " ");
}
});
System.out.println();
printInfo(s->System.out.print(s.split("\\,")[0]),
s->System.out.println(","+s.split("\\,")[1]),datas);
}
// 自身自销 有意思
private static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
for (String info : array) { // 这里每次产生 {迪丽热巴。性别:女 } String 数据 逻辑那边顺序处理就行
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。 } }
}
}
}
04.Supplier(供应者)
a.说明
无参,有返回值(供给型) Supplier<T>
b.示例
public class use_Supplier_Max_Value {
private static int getMax(Supplier<Integer> suply) {
return suply.get();
}
public static void main(String[] args) {
Integer [] data=new Integer[] {6,5,4,3,2,1};
int reslut=getMax(()->{
int max=0;
for (int i = 0; i < data.length; i++) {
max=Math.max(max, data[i]);
}
return max;
});
System.out.println(reslut);
}
}
05.Function(函数)
a.说明
有参,有返回值(函数型) Function<T>
b.示例
Main{String str = "赵丽颖,20";
solotion(s->s.split("\\,")[1],s->Integer.parseInt(s),s->s+=100,str);
}
private static void solotion(Function<String, String> o1, Function<String,
Integer> o2, Function<Integer, Integer> o3, String str) {
Integer apply = o1.andThen(o2).andThen(o3).apply(str);
System.out.println(apply);
}
06.Predicate(断言)
a.说明
断言
b.示例
public class Use_Predicate {
// 判断字符串是否存在o 即使生产者 又是消费者接口
private static void method_test(Predicate<String> predicate) {
boolean b = predicate.test("OOM SOF");
System.out.println(b);
}
// 判断字符串是否同时存在o h 同时
private static void method_and(Predicate<String> predicate1,Predicate<String> predicate2) {
boolean b = predicate1.and(predicate2).test("OOM SOF");
System.out.println(b);
}
//判断字符串是否一方存在o h
private static void method_or(Predicate<String> predicate1,Predicate<String> predicate2) {
boolean b = predicate1.or(predicate2).test("OOM SOF");
System.out.println(b);
}
// 判断字符串不存在o 为真 相反结果
private static void method_negate(Predicate<String> predicate) {
boolean b = predicate.negate().test("OOM SOF");
System.out.println(b);
}
public static void main(String[] args) {
method_test((s)->s.contains("O"));
method_and(s->s.contains("O"), s->s.contains("h"));
method_or(s->s.contains("O"), s->s.contains("h"));
method_negate(s->s.contains("O"));
}
}
4.3 this关键字
01.说明
a.介绍
Lambda 表达式并不会引入新的作用域,这一点和匿名内部类是不同的。
也就是说,Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。
b.示例
public class LamadaTest {
public static void main(String[] args) {
new LamadaTest().work();
}
public void work() {
System.out.printf("this = %s%n", this);
Runnable r = new Runnable()
{
@Override
public void run()
{
System.out.printf("this = %s%n", this);
}
};
new Thread(r).start();
new Thread(() -> System.out.printf("this = %s%n", this)).start();
}
}
02.work()可以分为3个部分
a.单独的 this 关键字
System.out.printf("this = %s%n", this);
其中 this 为 main() 方法中通过 new 关键字创建的 LamadaTest 对象——new LamadaTest()。
b.匿名内部类中的 this 关键字
Runnable r = new Runnable()
{
@Override
public void run()
{
System.out.printf("this = %s%n", this);
}
};
其中 this 为 work() 方法中通过 new 关键字创建的 Runnable 对象——new Runnable(){...}。
c.Lambda 表达式中的 this 关键字
其中 this 关键字和 1)中的相同。
d.我们来看一下程序的输出结果:
this = com.cmower.java_demo.journal.LamadaTest@3feba861
this = com.cmower.java_demo.journal.LamadaTest$1@64f033cb
this = com.cmower.java_demo.journal.LamadaTest@3feba861
4.4 方法引用符
01.说明
a.介绍
在 Java 中,:: 是方法引用操作符,用于简洁地引用类的方法或对象的方法。
它是一种函数式编程的语法糖,经常与 Java 8 引入的流(Streams)和 Optional 一起使用。
b.方法引用形式
a.静态方法引用
格式:ClassName::staticMethod
示例:Math::abs 引用的是 Math 类的静态方法 abs。
b.实例方法引用(特定实例)
格式:instance::instanceMethod
示例:myString::toUpperCase 引用的是 myString 对象的 toUpperCase 方法。
c.实例方法引用(任意实例)
格式:ClassName::instanceMethod
示例:Optional::isPresent 引用的是 Optional 类实例的 isPresent 方法。
d.构造器引用
格式:ClassName::new
示例:ArrayList::new 创建一个新的 ArrayList 实例。
02.示例
a.代码
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Optional<String>> optionalList = Arrays.asList(
Optional.of("Hello"),
Optional.empty(),
Optional.of("World")
);
// 使用方法引用过滤
List<Optional<String>> presentOptionals = optionalList.stream()
.filter(Optional::isPresent) // 方法引用
.collect(Collectors.toList());
System.out.println(presentOptionals);
}
}
b.说明
Optional::isPresent 的作用相当于提供了一个简化写法,等价于 optional -> optional.isPresent()。
filter(Optional::isPresent) 将只保留那些 Optional 不为空的元素。
5 Optional
5.1 定义
01.Optional类
Optional是个容器:它可以保存类型T的值,或者仅仅保存null。
Optional类的引入很好的解决空指针异常,可以不用显式进行空值检测。
Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
02.常用方法
创建:empty()、of()、ofNullable()
检查:isPresent()、ifPresent()
获取:get()、orElse()、orElseGet()、orElseThrow()
转换:map()、flatMap()
过滤:filter()
5.2 方法
00.汇总
a.创建Optional实例
static <T> Optional<T> empty(): 返回一个空的Optional实例。
static <T> Optional<T> of(T value): 返回一个包含指定非空值的Optional。
static <T> Optional<T> ofNullable(T value): 返回一个包含指定值的Optional,如果值为null,则返回一个空的Optional。
b.检查值
boolean isPresent(): 如果值存在,返回true;否则返回false。
void ifPresent(Consumer<? super T> consumer): 如果值存在,使用该值调用指定的消费者,否则不执行任何操作。
c.获取值
T get(): 如果Optional中有一个值,返回该值,否则抛出NoSuchElementException。
T orElse(T other): 返回值如果存在,否则返回other。
T orElseGet(Supplier<? extends T> other): 返回值(如果存在),否则调用other并返回该调用的结果。
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier): 返回包含的值(如果存在),否则抛出由提供的供应商创建的异常。
d.转换
<U> Optional<U> map(Function<? super T,? extends U> mapper): 如果存在一个值,则应用提供的映射函数,如果结果不为空,则返回一个Optional结果。
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper): 如果一个值存在,应用提供的Optional映射函数给它,返回该结果,否则返回一个空的Optional。
e.过滤
Optional<T> filter(Predicate<? super T> predicate): 如果一个值存在,并且该值与给定的谓词相匹配时,返回一个Optional描述的值,否则返回一个空的Optional。
01.创建Optional实例
a.静态方法ofNullable()
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
-------------------------------------------------------------------------------------------------
在实际开发中,数据很多是从数据库获取的,Mybatis从3.5版本已经支持Optional,直接把dao方法的返回值类型
定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回,封装的过程也不需要我们自己操作。
b.静态方法of()
Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);
-------------------------------------------------------------------------------------------------
一定要注意,如果使用of的时候传入的参数必须不为null。
如果你确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象。
c.静态方法empty()
Optional.empty()
如果一个方法的返回值类型是Optional类型,而如果我们经判断发现某次计算得到的返回值为null,
这个时候就需要把null封装成Optional对象返回。这时则可以使用Optional的静态方法empty来进行封装。
02.检查值
a.isPresent
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent()) {
System.out.println(authorOptional.get().getName());
}
b.ifPresent
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.ifPresent(author -> System.out.println(author.getName()));
03.获取值
a.介绍
如果想获取值自己进行处理可以使用get方法获取,但是不推荐,因为当Optional内部的数据为空的时候会出现异常。
如果我们期望安全的获取值,我们不推荐使用get方法,而是使用Optional提供的以下方法。
b.orElseGet
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
Author author1 = authorOptional.orElseGet(() -> new Author());
-----------------------------------------------------------------------------------------------------
获取数据并且设置数据为空时的默认值。
如果数据不为空就能获取到该数据,如果为空则根据你传入的参数来创建对象作为默认值返回。
c.orElseThrow
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
try {
Author author = authorOptional.orElseThrow(
(Supplier<Throwable>) () -> new RuntimeException("author为空")
);
System.out.println(author.getName());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
-----------------------------------------------------------------------------------------------------
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
04.转换
a.介绍
Optional提供map对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证使用安全。
b.代码
private static void testMap() {
Optional<Author> authorOptional = getAuthorOptional();
Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());
optionalBooks.ifPresent(books -> System.out.println(books));
}
05.过滤
a.介绍
使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
b.代码
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(author -> author.getAge()>100)
.ifPresent(author -> System.out.println(author.getName()));