Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。Java具有简单性、面向对象、分布式健壮性安全性、平台独立与可移植性、多线程、动态性等特点。 Java可以编写桌面应用程序、Web应用程序、分布式系统嵌入式系统应用程序等

JDK安装下载

下载jdk-17.0.12_windows-x64_bin.zip, 安装时可以选择安装地址,安装完成后配置JDK17的环境变量:

1
2
3
新增系统变量:JAVA_HOME,值为D:\IDEA\Java\jdk-17(安装路径)
新增系统变量:CLASSPATH,值为.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar,注意最前面的小黑点不能省.
在系统变量Path下新增两条配置:%JAVA_HOME%\bin和%JAVA_HOME%\jre\bin

其他版本安装差不多,下载地址Java downloads


Java8 新特性

原文链接:https://blog.csdn.net/qq_40991313/article/details/137256095

1.基本介绍

  1. Lambda表达式:Lambda表达式可以被视为一个对象,必须有上下文环境,作用是实现单方法的接口。该特性可以将功能视为方法参数,或者将代码视为数据。上下文环境意思是能证明它是对象,如让它处在方法或类的实参里,或者赋值给对象引用。
    • 省略情况:形参类型、返回类型可以省略,单参数能省略小括号,单语句能省略return、分号和大括号(全省略或全不省略)
  2. **方法引用**:引用已存在的Lambda表达式,达到相同的效果。引用已有Java类或对象(实例)的静态方法、实例方法、对象方法(System.out::println;)、构造器方法。可与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  3. **接口默认方法**:允许在接口中定义默认方法,默认方法必须使用default修饰。默认方法是接口中有方法体的方法,用于向已有的接口添加新的功能,而无需破坏现有的实现。实现类可以直接调用默认方法,也可以重写默认方法。
  4. **Stream API**:新添加的Stream API(java.util.stream)支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作(stream流的生成、操作、收集),例如filter()过滤、distinct()去重、map()加工、sorted()排序等操作。
  5. Date Time API新增LocalDate、LocalTime、DateTimeFormatter等类:加强对日期与时间的处理。LocalDate、LocalTime可以获取本地时间。线程安全的DateTimeFormatter代替线程不安全的SimpleDateFormat,用于将日期和字符串之间格式转换。
  6. HashMap底层引入红黑树:之前版本HashMap底层是“数组+链表”,当头插法的value链表长度大于等于8时,链表会转为红黑树,红黑树查询性能稳定O(logn),是近似平衡二叉树,层数最高2logn。
  7. ConcurrentHashMap降低锁的粒度:JDK1.8之前采用分段锁,锁粒度是分段segment,JDK1.8采用synchronized+CAS,锁粒度是槽(头节点)
  8. CompletableFuture:是Future的实现类,JDK8引入,用于异步编排。
  9. JVM方法区的实现方式由永久代改为元空间:元空间属于本地内存,由操作系统直接管理,不再受JVM管理。同时内存空间可以自动扩容,避免内存溢出。默认情况下元空间可以无限使用本地内存,也可以通过-XX:MetaspaceSize限制内存大小。

2.Lambda表达式

Lambda 表达式是 Java 8 引入的一种新特性,主要用于实现单方法的接口,符合函数式思想。

函数式思想:
在数学中,函数是一套计算方案,包含输入量和输出量,即“拿数据做操作”。
面向对象思想强调通过对象的形式来完成任务,而函数式思想则尽量忽略面向对象的复杂语法,强调“做什么”,而不是“以什么形式去做”。
Lambda表达式正是函数式思想的体现。它用来实现单方法接口的方法逻辑,实现过程中的参数可省略类型(因为类型已经在接口里声明过了),单个表达式时可以省略分号和大括号(因为只有单个语句,不用分号分割也不会有歧义)。

Lambda表达式实际是一个对象,形式是一个方法的逻辑:():{}。

例如前面TreeSet那块有提到过:

1
2
3
4
5
6
7
8
9
10
// 匿名内部类形式
TreeSet<Dog> dogs = new TreeSet<>(new Comparator<Dog>() {
@Override
public int compare(Dog a, Dog b) {
return a.weight - b.weight;
}
});
// 可以用Lambda表达式简化成
// Lambda表达式形式
TreeSet<Dog> dogs = new TreeSet<>((a, b) -> a.weight - b.weight);

Lambda和匿名内部类区别:

  • 接口:Lambda只能是接口,匿名内部类可以是接口、抽象类、具体类。
  • 方法:Lambda只能单方法,匿名内部类可以多方法。
  • 原理:Lambda编译后字节码在运行时动态生成,匿名内部类编译后生成一个字节码文件

基本语法

1
2
3
(参数列表) -> 单个表达式

(参数列表) -> { 表达式1;表达式2; }

可省略内容:

  • 形参类型、返回类型可以省略;
  • 单参数能省略小括号;
  • 单语句能省略return、分号和大括号(全省略或全不省略)

代码示例:

  • 形参类型、返回类型可以省略。毕竟接口里也声明了类型,类型必须要么都写要么都省略。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo {
public static void main(String[] args) {
// 使用Lambda表达式前,需要再创建一个Dog实现类,然后创建对象传入fun()方法
// 使用Lambda表达式后,直接一行代码就解决了,Lambda表达式本身是实现了add()方法逻辑的Dog对象
// Lambda形参类型、返回类型可以省略
fun((a,b)->a+b);
}
public static void fun(Dog d){
d.add(1,4);
}
}

// 单方法接口
public interface Dog {
/**
* 只能有一个方法
* @param a
* @param b
* @return int
*/
public int add(int a,int b);
}

3. Stream流

3.1 基本介绍

Stream 流是 Java 8 引入的一项新特性,用于对集合进行函数式编程风格的操作。它允许我们以声明性方式对数据进行过滤、加工、遍历、排序等操作,而不是以命令式方式逐个操作元素。

特点

  • 声明式编程:使用流操作表达数据处理的意图,而不是具体的实现。声明式编程强调“做什么”而不是“怎么做”。在声明式编程中,程序员专注于描述要完成的任务或问题,而不是描述具体的实现步骤。Stream流把复杂啰嗦的代码改成一串简洁的代码,符合声明式编程。
  • 链式操作:流操作可以链式调用,使代码更加简洁和易读。链式操作是利用运算符进行的连续运算(操作),如连续的赋值操作,而非拆成多条语句一个个加。例如stringBuilder.append(1).append(2);是链式操作,而stringBuilder.append(“a”);stringBuilder.append(“b”);不是链式操作。
  • 惰性求值:流操作是惰性求值的,即只有在终端操作才会执行实际计算,map等中间操作不会直接计算求值。
  • 并行处理:遍历的每一层之间是并发处理的,性能高。

代码示例: 先获取List的stream流,给集合中每个元素+2,然后升序排序、过滤只保留大于20的元素,最后收集成List类型的集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(20);
arrayList.add(30);
arrayList.add(40);
arrayList.add(50);
// 使用 Stream API 给每个元素加 2 并生成新的列表
System.out.println("使用 Stream API 遍历并修改元素:");
List<Integer> modifiedList = arrayList.stream()
.map(num -> num + 2).sorted().filter(num -> num > 20)
.collect(Collectors.toList());
// 打印修改后的列表
System.out.println(modifiedList);
}
}
// 输出结果
// 使用 Stream API 遍历并修改元素:
// [22,32,42,52]

3.2 常用方法

3.2.1 生成操作

生成一个流

  • Stream.of(T... values):通过提供的值创建一个流。
  • Stream.ofNullable(T t):通过单个值创建一个包含该值的流或一个空流。
  • Arrays.stream(T[] array):通过数组创建一个流。
  • Collection.stream():通过集合创建一个顺序流。
  • Collection.parallelStream():通过集合创建一个并行流。
  • Stream.generate(Supplier<T> s):生成一个无限流,元素由提供的 Supplier 生成。
  • Stream.iterate(T seed, UnaryOperator<T> f):生成一个无限流,初始元素为 seed,后续元素由一元运算符生成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 示例代码展示如何生成 Stream 流
public class StreamGeneration {
public static void main(String[] args) {
// Collection 体系:可以便用默认方法 stream() 生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
// Map 体系:间接的生成流
Map<String, Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
// 数组体系:可以通过 Stream 接口的静态方法 of(T... values) 生成流
String[] strArray = {"hello", "world", "java"};
Stream<String> strArrayStream = Stream.of(strArray);
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> intStream = Stream.of(10, 20, 30);
}
}

3.2.2 中间操作

中间操作返回一个新的流,可以链式调用多个中间操作。

  • filter(Predicate predicate):过滤元素
  • map(Function<T, R> mapper):加工,每个元素处理后返回一个新的元素。
    • map和filter区分:filter是满足条件的留下,是对原数组的过滤;map则是对原数组的加工,映射成一对一映射的新数组。
  • mapToInt(ToIntFunction<? super T> mapper):返回一个 IntStream,除常规流式操作外,还可以取和、平均数等Integer的操作;
  • concat():连接
  • skip(long n):跳过n个元素
  • distinct():去重
  • sorted(比较器):排序
  • limit(long maxSize):只截取前maxSize个元素

filter:过滤

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤掉空字符串,打印非空字符串。filter 的参数是 Lambda,Lambda 参数是 list 的每个元素,返回值是 Boolean,为 true 时留下。

1
2
3
List<String> list = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// forEach 终结后,原 list 不变,处理后的 list 遍历输出
list.stream().filter(item -> !item.isEmpty()).forEach(System.out::println);

skip & limit:跳过、限制

1
2
3
List<String> list = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// 需求: 跳过 2 个元素,把剩下的元素中前 2 个在控制台输出
list.stream().skip(2).limit(2).forEach(System.out::println);

concat 和 distinct:连接和去重

1
2
3
Stream<String> s1 = Stream.of("a", "b", "c");
Stream<String> s2 = Stream.of("b", "c", "d");
Stream.concat(s1, s2).distinct().forEach(System.out::println);

sorted 比较器:

sorted() 方法返回由此流的元素组成的流,根据自然顺序排序。如:按字母序排序(sorted()参数可省,因为默认就是按字母序排序)

1
2
3
4
5
6
7
List<String> list = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// 按字母序排序(默认)
// forEach 终结后,原 list 不变,处理后的 list 遍历输出
list = list.stream().sorted((item1, item2) -> {
return item1.compareTo(item2);
}).collect(Collectors.toList());
System.out.println(list);

map

中间操作 map() 用法和 filter 类似,map 的参数是 Lambda,Lambda 参数是 list 的每个元素,返回值是加工后的 list 元素。

1
2
3
4
List<String> list = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// collect 收集后原 list 不变,处理后的 list 收集成返回值
list = list.stream().map(item -> item.toUpperCase()).collect(Collectors.toList());
System.out.println(list);

mapToInt

mapToInt 返回一个 IntStream,其中包含将给定函数应用于此流的元素的结果。IntStream 是由 Integer 元素组成的流,可以取和、平均数、排序、skip、limit、count、forEach 等。

1
2
3
4
5
6
List<String> list = Arrays.asList("10", "20", "30");
// 输出每个元素
list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
// 计算元素的总和
int sum = list.stream().mapToInt(Integer::parseInt).sum();
System.out.println(sum);

3.2.3 终端操作

终端操作触发流的计算,并返回一个结果或副作用。

  • forEach(Consumer action):对每个元素执行操作
  • collect(Collector<T, A, R> collector):收集流的元素到一个集合或其他类型
  • count():计算流中的元素数量

countcount() 方法返回此流中的元素数量。

1
2
3
List<String> list = Arrays.asList("10", "20", "30");
long count = list.stream().mapToInt(Integer::parseInt).count();
System.out.println(count); // 输出 3

forEachforEach(IntConsumer action) 方法对此流的每个元素执行操作。

1
2
List<String> list = Arrays.asList("10", "20", "30");
list.stream().mapToInt(Integer::parseInt).forEach(value -> System.out.println(value));

collectcollect(Collector<T, A, R> collector):收集流的元素到一个集合或其他类型

  • public static Collector toList(): 把元素收集到 List 集合中
  • public static Collector toSet(): 把元素收集到 Set 集合中
  • public static Collector toMap(Function keyMapper, Function valueMapper): 把元素收集到 Map 集合中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<String> list = new ArrayList<>();
list.add("123");
list.add("4567");
// 1.过滤只保留长度为3的字符串,收集成List<String>类型
List<String> ansList = list.stream().filter(item -> item.length() == 3).collect(Collectors.toList());
System.out.println(ansList);
// 2.收集成List<Integer>类型
List<Integer> ansList1 = list.stream().map(Integer::parseInt).collect(Collectors.toList());
System.out.println(ansList1);
// 3.收集成Set<Integer>类型
Set<Integer> ansList2 = list.stream().map(Integer::parseInt).collect(Collectors.toSet());
System.out.println(ansList2);
// 4.收集成Map<String,Integer>类型,key是字符串,value是长度
Map<String, Integer> stringSizeMap = list.stream().collect(Collectors.toMap(item -> item, item -> item.length()));
System.out.println(stringSizeMap);
// 5.收集成String类型,按逗号分割
String ansString = list.stream().collect(Collectors.joining(","));
System.out.println(ansString);

4. 函数式接口

4.1 基本介绍

函数式接口是指仅包含一个抽象方法的接口,可以使用 Lambda 表达式或方法引用来创建该接口的实例。Java 8 引入了函数式接口,并在java.util.function 包中提供了许多预定义的函数式接口,例如 Function、Supplier、Consumer、Predicate 等。

函数式接口:有且仅有一个抽象方法的接口。 Java中函数式编程体现就是Lambda表达式。

函数式接口注解@FunctionalInterface

如果方法的返回值是函数式接口,可以使用Lambda表达式作为结果返回。

格式: 函数式接口对象作为一个方法的形参,实参是Lambda表达式。

常见的函数式接口

JDK自带一些函数式接口:函数式接口Supplier中get获取有返回值的数据,Predicate中test返回boolean型数据,Consumer中accept无返回值数据操作。

接口 方法 作用 示例
Supplier get(),返回各类型的数据 获取 返回数组最大值
Function apply(),返回各类型数据 转换 数字字符串转换
Predicate test(),返回boolean型数据 判断 判断数字是否大于5
Consumer accept(),无返回值 操作 翻转字符串

4.2 生产者接口Supplier

作用:获取到生产出的数据

Supplier译作生产者,生产的数据类型由泛型参数T指定。

示例:例如生产者接口,创建一个方法,获取数组的最大值,实际的获取逻辑由Lambda表达式确认:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test {
public static void main(String[] args) {
// 定义一个int数组
int[] arr = {19, 50, 28, 37, 46};
// 获取数组中的最大值
int maxValue = getMax(() -> {
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
return max;
});

// 输出最大值 50
System.out.println("数组中的最大值是: " + maxValue);
}
/**
* 返回一个int数组中的最大值
* @param sup Supplier<Integer>函数式接口
* @return int数组中的最大值
*/
private static int getMax(Supplier<Integer> sup) {
return sup.get();
}
}

4.3 函数式接口Consumer

作用:对数据进行操作,无返回值。

Consumer是消费型接口,消费的数据类型由泛型指定。

代码示例:两种方式和一种方式消费字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Test {
public static void main(String[] args) {
String name = "Hello World";
// 使用两种方式消费字符串
operatorString(name, str -> System.out.println("转大写:" + str.toUpperCase()), str ->
System.out.println("转小写:"+str.toLowerCase()));
// 使用一个方式消费字符串
operatorString(name, str -> System.out.println("空格替换成下划线:" + str.replace(" ", "_")));
}
/**
* 定义一个方法,用两种方式消费同一个字符串数据,共消费5次
* @param name 字符串数据
* @param con1 第一个消费者
* @param con2 第二个消费者
*/
private static void operatorString(String name, Consumer<String> con1, Consumer<String> con2) {
// 第一个消费者消费一次
System.out.println("第一个消费者消费一次");
con1.accept(name);
// 第二个消费者消费一次
System.out.println("第二个消费者消费一次");
con2.accept(name);
// 第一个消费者组合第二个消费者,再组合第一个消费者,按顺序各消费一次
System.out.println("第一个消费者组合第二个消费者,再组合第一个消费者,按顺序各消费一次");
con1.andThen(con2).andThen(con1).accept(name);
}
/**
* 定义一个方法,消费一个字符串
* @param name 字符串数据
* @param con 消费者
*/
private static void operatorString(String name, Consumer<String> con) {
con.accept(name);
}

}

4.4 函数式接口Predicate

Predicate接口用于表示布尔值函数,判断参数是否满足条件。它接受一个参数,并返回一个布尔值。

译作谓词,谓语。

方法签名 说明
boolean test(T t) 对给定的参数进行判断(谓词操作)。返回布尔值。
default Predicate and(Predicate<? super T> other) 返回一个组合的 Predicate,表示逻辑与(AND)操作。
default Predicate negate() 返回一个表示逻辑非(NOT)操作的 Predicate。
default Predicate or(Predicate<? super T> other) 返回一个组合的 Predicate,表示逻辑或(OR)操作。

方法的形参Predicate,接受的实参是Lambda参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Test {
public static void main(String[] args) {
// 测试字符串长度是否在5到100之间
// false
boolean result = checkString("hello",
s -> s.length() > 5,
s -> s.length() < 100);
System.out.println(result);
// 测试字符串是否长度不等于5
// false
System.out.println(negateCheck("hello", s->s.length() == 5));
}
/**
* 检查字符串是否满足两个谓词的逻辑与条件
* @param s 要检查的字符串
* @param p1 第一个谓词
* @param p2 第二个谓词
* @return 如果字符串满足两个谓词的逻辑与条件,返回true;否则返回false
*/
private static boolean checkString(String s, Predicate<String> p1, Predicate<String> p2) {
return p1.and(p2).test(s);
}
/**
* 检查字符串是否不满足谓词条件
* @param s 要检查的字符串
* @param p 谓词
* @return 如果字符串不满足谓词条件,返回true;否则返回false
*/
private static boolean negateCheck(String s, Predicate<String> p) {
return p.negate().test(s);
}
}

4.5 方法引用

4.5.1 概念

方法引用跟Lambda类似,都可以根据上下文推导。格式:

1
引用类::不带括号的方法名

4.5.2 引用实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class Test {
public static void main(String[] args) {
// 1.使用Lambda表达式
TreeSet<Dog> dogsLambda = new TreeSet<>((a, b) -> a.weight - b.weight);
// 1.使用方法引用,引用compareDogsByWeight()方法
TreeSet<Dog> dogsMethodRef = new TreeSet<>(Test::compareDogsByWeight);

// 添加一些示例数据
dogsLambda.add(new Dog(10, "旺财"));
dogsLambda.add(new Dog(5, "小黑"));
dogsLambda.add(new Dog(15, "小白"));
dogsMethodRef.add(new Dog(10, "旺财"));
dogsMethodRef.add(new Dog(5, "小黑"));
dogsMethodRef.add(new Dog(15, "小白"));
// 打印排序结果
System.out.println("1.使用Lambda表达式打印结果:");
dogsLambda.forEach(dog->{
System.out.println(dog);
});
System.out.println("2.使用方法引用,引用PrintStream类的println方法:");
dogsMethodRef.forEach(System.out::println);
}
/**
* 比较两个Dog对象的体重
* @param a 第一个Dog对象
* @param b 第二个Dog对象
* @return 返回体重差
*/
public static int compareDogsByWeight(Dog a, Dog b) {
return a.weight - b.weight;
}
}

// 狗类:按比较器排序时不需要再实现Comperable<>
public class Dog {
int weight;
String name;

public Dog(int weight, String name) {
this.weight = weight;
this.name = name;
}

@Override
public String toString() {
return "Dog{" +
"weight=" + weight +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dog dog = (Dog) o;
return weight == dog.weight && Objects.equals(name, dog.name);
}

@Override
public int hashCode() {
return Objects.hash(weight, name);
}
}

打印结果

1.使用Lambda表达式打印结果:
Dog{weight=5, name=’小黑’}
Dog{weight=10, name=’旺财’}
Dog{weight=15, name=’小白’}
2.使用方法引用,引用PrintStream类的println方法:
Dog{weight=5, name=’小黑’}
Dog{weight=10, name=’旺财’}
Dog{weight=15, name=’小白’}

4.6 引用构造器

某些场景下,需要在使用时才确定创建对象的逻辑,这时就可以引用构造器。格式:

1
类名::new

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Test {
public static void main(String[] args) {
// 使用 Lambda 表达式
builderLinQinXia((name, age) -> new Student(name, age));
builderZhangXueYou((name, age) -> new Student(name, age));
// 使用构造方法引用
builderLinQinXia(Student::new);
builderZhangXueYou(Student::new);
}
/**
* 使用 StudentBuilder 创建 Student 对象,对象的name是林青霞
* @param sb StudentBuilder 接口的实例
*/
private static void builderLinQinXia(StudentBuilder sb) {
Student s = sb.build("林青霞", 30);
System.out.println(s.getName() + " " + s.getAge());
}
/**
* 使用 StudentBuilder 创建 Student 对象,对象的name是张学友
* @param sb StudentBuilder 接口的实例
*/
private static void builderZhangXueYou(StudentBuilder sb) {
Student s = sb.build("张学友", 26);
System.out.println(s.getName() + " " + s.getAge());
}
}

// Student 类
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
// 想要在使用时再确认对象的创建逻辑,就需要创建一个StudentBuilder接口,再使用时通过Lambda或者方法引用对它进行实现:
@FunctionalInterface
interface StudentBuilder {
Student build(String name, int age);
}

4.7 接口组成更新

java8后加入了默认方法、静态方法。java9后加入了私有方法。

4.7.1 接口默认方法

接口中的方法会被隐式指定为 public abstract方法,抽象方法不能有实际内容。但某些业务场景需在接口中扩展一些有业务逻辑的方法,又不想直接改成抽象类(因为抽象类只能单继承),这时,JDK8扩展了接口的默认方法,需要用default关键字修饰方法。格式

default 返回值 方法名(参数) {
    ...
}

特点:

  • 默认方法可以有方法体
  • 默认方法可被接口的实现类对象调用。
  • 默认方法不是抽象方法,实现类可以重写,也可以不重写,同名冲突时必须重写(某个类实现了多个包含默认方法fun()的接口,为了防止冲突,此时实现类必须重写这个类)。

示例:

1
2
3
4
5
6
7
8
9
// 接口
public interface MyInterface {
// 抽象方法,默认public abstract修饰
void abstractMethod();
// 默认方法,使用default修饰
default void defaultMethod() {
System.out.println("这是接口的默认方法");
}
}

4.7.2 接口静态方法

在 Java 8 中,引入了接口静态方法。接口静态方法与类静态方法类似,但它们属于接口本身,而不是接口的实现类。静态方法常用于在接口中提供静态工具方法,帮助操作或处理与接口相关的数据。格式与类的静态方法一致:

static 返回类型 方法名(参数列表) {
    // 方法体
}

特点

  • 命名冲突时要重写:因为Java是单继承多实现的,所以存在一个类实现多个接口的情况。如果一个类同时实现了多个接口,并且这些接口都有相同签名的默认方法,则必须在实现类中重写该方法以解决冲突。例如类A实现了有同名静态方法的接口B、C,则A的对象分不清调用哪个接口的方法。
  • 扩展接口功能:默认方法允许在不破坏现有实现的情况下扩展接口功能,对现有功能影响较小,符合设计模式中的开闭原则。
  • 接口中静态方法跟类的静态方法,属于“类方法”,先于对象产生,所以只能被接口名调用,不能被实现类对象调用。

示例

1
2
3
4
5
6
public interface MyInterface {
// 静态方法
static void staticMethod() {
System.out.println("这是接口的静态方法");
}
}

4.7.3 JDK9接口私有方法

接口中的方法会被隐式的指定为 public abstract方法。为了将接口中多个默认或静态方法中的共性方法抽离出来,JDK9引入了接口私有方法。接口私有方法只能在接口内部,被接口静态方法或者接口默认方法调用,不能在接口外部或实现类中调用。格式

1
2
3
4
5
6
7
private 返回类型 方法名(参数列表) {
// 方法体
}
// 或者
private static 返回类型 方法名(参数列表) {
// 方法体
}

特点

  • 接口私有方法可以是普通私有方法,也可以是静态私有方法;

  • 只能被接口内静态、默认方法调用;

  • 必须JDK9及以上版本;

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 狗类:可以重写默认方法,但这里我们不需要重写
public class Dog implements Animal {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
Animal.staticMethod();
}
}
// 动物接口
public interface Animal {
default void eat() {
System.out.println("动物在吃东西");
privateSoundMethod();
}
// 私有实例方法:发出声音
private void privateSoundMethod() {
System.out.println("这是私有实例方法,接口内私有、静态、默认方法都可以调用它:发出声音");
}
// 静态方法
static void staticMethod() {
System.out.println("静态方法调用私有静态方法");
privateStaticMethod();
}
// 私有静态方法
private static void privateStaticMethod() {
System.out.println("这是私有静态方法,它只能被接口中的静态方法调用");
}
}

运行结果:

静态方法调用私有静态方法
这是私有静态方法,它只能被接口中的静态方法调用
动物在吃东西
这是私有实例方法,接口内私有、静态、默认方法都可以调用它:发出声音


Java11 新特性

原文链接:https://blog.csdn.net/qq_43947876/article/details/148399400

一、标准化HTTP Client:告别HttpURLConnection

1.1 HttpURLConnection 的痛点

在 Java 11 之前,开发者主要使用 HttpURLConnection 处理 HTTP 请求,但存在严重缺陷:

  1. 设计陈旧

    • 同步阻塞模型导致性能瓶颈。

    • 基于 JDK 1.1 时代的设计。

    • 繁琐的流处理(需手动管理 InputStream/OutputStream)。

  2. 功能缺失

    • 不支持 HTTP/2。

    • 缺乏异步处理能力。

    • 无内置的 WebSocket 支持。

    • 难以处理 cookie 和重定向。

  3. 代码冗长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 传统 HttpURLConnection 示例 (GET请求)
URL url = new URL("https://api.example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

int status = conn.getResponseCode(); // 同步阻塞点
if (status == 200) {
try (BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
StringBuilder content = new StringBuilder();
while ((line = in.readLine()) != null) {
content.append(line);
}
System.out.println(content);
}
} else {
// 错误处理...
}
conn.disconnect();

1.2 标准化 HTTP Client 的解决方案

核心设计原理:

image-20250722121944021

Java 11 的标准化 HTTP Client 采用分层异步架构,核心通过 协议协商机制 自动选择最佳协议(优先尝试 HTTP/2,失败时降级到 HTTP/1.1),利用 多路复用技术 在单个 TCP 连接上并行处理多个请求,通过 响应式流处理模型 实现非阻塞 I/O 操作。其内部基于 CompletableFuture 实现异步处理引擎,配合智能 连接池管理 减少连接建立开销,同时通过 类型安全的 BodyHandler/BodyPublisher 抽象层实现请求/响应体的灵活转换,最终解决了传统 HttpURLConnection 的同步阻塞、协议支持不足和 API 臃肿三大痛点,为现代网络编程提供了高性能、可扩展的标准解决方案。

1.3 实战应用指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 1.基础配置
// 创建可复用的 HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 强制使用 HTTP/2
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.ALWAYS) // 自动重定向
.authenticator(Authenticator.getDefault()) // 认证支持
.build();

// 2.同步请求示例
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token")
.timeout(Duration.ofSeconds(15))
.GET() // 默认方法,可省略
.build();

// 同步处理
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("状态码: " + response.statusCode());
System.out.println("响应体: " + response.body());

// 3.异步请求示例
// 异步处理
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(body -> {
System.out.println("异步响应: " + body);
// 处理业务逻辑
})
.exceptionally(e -> {
System.err.println("请求失败: " + e.getMessage());
return null;
});

// 主线程继续执行其他任务...

// 4.POST 请求(JSON 提交)
String jsonBody = "{\"name\":\"John\", \"age\":30}";

HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();

// 5.文件上传
HttpRequest fileUpload = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/upload"))
.header("Content-Type", "multipart/form-data")
.POST(HttpRequest.BodyPublishers.ofFile(Paths.get("data.txt")))
.build();

// 6.WebSocket 支持
WebSocket webSocket = HttpClient.newHttpClient()
.newWebSocketBuilder()
.buildAsync(URI.create("wss://echo.websocket.org"), new WebSocket.Listener() {

@Override
public CompletionStage<?> onText(WebSocket webSocket,
CharSequence data,
boolean last) {
System.out.println("收到消息: " + data);
return null;
}

@Override
public void onOpen(WebSocket webSocket) {
webSocket.sendText("Hello Server!", true);
}
}).join();

1.4 使用建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 客户端复用:全局创建单个 HttpClient 实例(线程安全),避免每次请求创建新客户端。
// 2. 超时控制
.timeout(Duration.ofSeconds(10)) // 请求超时
.connectTimeout(Duration.ofSeconds(5)) // 连接超时

// 3. 响应处理
// 3.1 处理大文件
HttpResponse<Path> response = client.send(
request,
HttpResponse.BodyHandlers.ofFile(Paths.get("output.txt"))
);
// 3.2 流式处理
HttpResponse<InputStream> streamResponse = client.send(
request,
HttpResponse.BodyHandlers.ofInputStream()
);

// 4. 错误处理
try {
HttpResponse<String> response = client.send(...);
} catch (IOException | InterruptedException e) {
// 处理IO异常
} catch (HttpTimeoutException e) {
// 处理超时
}

1.5 完整 HTTP Client 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AdvancedHttpClientExample {
private static final HttpClient CLIENT = HttpClient.newHttpClient();

public CompletableFuture<JsonObject> fetchUserData(String userId) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/" + userId))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(8))
.build();

return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
if (response.statusCode() == 200) {
return parseJson(response.body());
} else {
throw new RuntimeException("HTTP Error: " + response.statusCode());
}
});
}

private JsonObject parseJson(String body) {
// 使用JSON解析库处理
return ...;
}
}

1.6 总结

Java 11 的标准化 HTTP Client 解决了传统网络编程的三大痛点:

  • 协议落后 → 支持 HTTP/2 和 WebSocket
  • 性能低下 → 异步非阻塞 + 连接复用
  • API 繁琐 → 链式调用 + 响应式处理

二、局部变量类型推断增强:Lambda中的var

先说一下什么是java中的var类型,Java 中的 var 是 JDK 10
引入的‌局部变量类型推断‌关键字,允许编译器根据赋值语句右侧的表达式自动推断变量类型,从而简化代码编写。

2.1 解决的问题:类型声明的一致性困境

在 Java 10 引入 var 后,开发者面临一个矛盾:

1
2
3
4
5
6
// 普通局部变量可以使用 var
var list = new ArrayList<String>(); // ✅ Java 10+

// 但 Lambda 参数不能使用 var
(var s) -> s.length() // ❌ Java 10 编译错误
(s) -> s.length() // ✅ 传统写法

这种不一致性就导致了:

  • 代码风格割裂:普通变量和 Lambda 参数使用不同规则。
  • 注解无法应用:无法给隐式类型参数添加注解。
  • 可读性降低:复杂 Lambda 难以理解参数类型。

2.2 实现原理:编译器的类型推导增强

Java 11 通过 JEP 323 扩展了编译器的类型推导能力:

image-20250722122008694

这种方式根据方法签名推导参数类型,通过函数式接口传递类型信息并且允许在 var 上添加注解。 编译过程

1
2
3
4
5
6
7
// 代码
Function<String, Integer> len = (var s) -> s.length();

// 编译器处理
1. 根据左侧 Function<String, Integer> 确定目标类型
2. 解析 var s → 推导为 String 类型
3. 验证 s.length() 返回 int (兼容 Integer)

编译器处理步骤

  1. 词法分析:识别 var 关键字

  2. 语法树构建:创建带 var 的 Lambda 节点

  3. 类型绑定

    • 从赋值上下文获取目标类型

    • 解析函数式接口的泛型参数

    • 将 var 绑定到具体类型

  4. 注解附加:将参数注解关联到具体类型

2.3 实战应用指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 基础用法:启用带注解的类型声明
// 1.1 Java 11 新特性
Function<String, Integer> lengthFunc =
(@NonNull var str) -> str.length(); // ✅ 可添加注解
// 1.2 传统写法对比
Function<String, Integer> oldFunc =
str -> str.length(); // ❌ 无法添加注解

// 2. 复杂场景:多参数类型推断
// 2.1 多参数场景
BiFunction<Integer, Double, String> format =
(var num, var dec) -> String.format("%d-%.2f", num, dec);
// 2.2 等价显式声明
BiFunction<Integer, Double, String> explicit =
(Integer num, Double dec) -> ...;

// 3. 类型敏感操作:强制类型检查
List<Object> mixedList = Arrays.asList("Text", 42, 3.14);
mixedList.forEach((var obj) -> {
if (obj instanceof String) {
System.out.println(((String) obj).toUpperCase()); // 安全转型
}
});

适用场景:

1
2
3
4
5
6
7
8
9
10
// 1. 需要参数注解时
(var @Email email) -> validate(email)

// 2. 复杂泛型嵌套时
Map<String, List<Map<Integer, Set<String>>>> complex = ...;
complex.forEach((var key, var value) -> ...);

// 3. 明确参数类型意图时
// 明确表示参数应有类型声明
(var userId, var timestamp) -> process(userId, timestamp)

禁用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误1:单独使用 var 无法推导类型
var lambda = (var x) -> x * 2; // ❌ 缺少目标类型

// 错误2:混合显式和隐式声明
(var x, y) -> x + y // ❌ 不允许混合使用

// 错误3:无法推导冲突类型
Function<Number, String> func =
(var n) -> n.toString(); // ✅ 正确
Consumer<String> consumer =
(var n) -> System.out.println(n); // ✅ 正确

// 但这样会冲突:
Object ambiguous = (var n) -> n.toString(); // ❌ 目标类型不明确

2.4 终极示例:结合注解和复杂类型

Java 11 的这项改进并非鼓励在所有 Lambda 中使用 var,而是提供选择性显式类型声明的能力,在保持类型安全的同时增加灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserProcessor {
public void processUsers(List<@Valid User> users) {
users.forEach((var user) -> {
@NonEmpty String name = validateName(user.getName());
sendWelcomeEmail(user.getEmail());
});
}

private String validateName(@NonEmpty String name) {
if(name.isBlank()) throw new IllegalArgumentException();
return name.strip();
}
}

三、文件读写简化

3.1 文件操作的”样板代码地狱”

在 Java 11 之前,读写文件需要大量重复代码,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Java 11 前读取文件
Path path = Paths.get("demo.txt");
String content;
try (BufferedReader reader = Files.newBufferedReader(path)) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
content = sb.toString();
} catch (IOException e) {
// 异常处理
}

// Java 11 前写入文件
List<String> lines = Arrays.asList("Line1", "Line2");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
// 异常处理
}

传统方式存在几大痛点:

  1. 代码冗长:简单操作需要10+行代码
  2. 资源管理复杂:必须显式关闭流(忘记关闭会导致资源泄漏)
  3. 异常处理繁琐:必须处理 IOException
  4. 字符集问题:默认使用平台编码,跨平台可能乱码

3.2 实现原理:智能封装与最佳实践

Java 11 通过 Files 类新增方法实现简化:

1
2
3
4
5
6
7
8
9
10
11
12
// 源码核心实现 (简化版)
public static String readString(Path path) throws IOException {
return readString(path, StandardCharsets.UTF_8); // 默认UTF-8
}

public static Path writeString(Path path, CharSequence csq, OpenOption... options)
throws IOException {
try (BufferedWriter writer = newBufferedWriter(path, UTF_8, options)) {
writer.write(csq.toString());
}
return path;
}

关键技术原理

  1. 自动资源管理: 使用 try-with-resources 确保流自动关闭
  2. 智能缓冲: 内部使用 BufferedWriter/BufferedReader 优化性能
  3. 字符集安全: 默认 UTF-8 解决跨平台乱码问题
  4. 异常透明: 保留必要的 IOException 传播

3.3 实战应用指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 1. 基础读写操作
// 1.1 写入文件 (覆盖模式)
Path file = Path.of("data.txt");
Files.writeString(file, "Hello Java 11!");
// 1.2 追加写入
Files.writeString(file, "\n追加内容", StandardOpenOption.APPEND);
// 1.3 读取文件
String content = Files.readString(file);
System.out.println(content);
// 1.4 输出:
// Hello Java 11!
// 追加内容

// 2. 字符集控制
// 2.1 指定字符集写入
Files.writeString(file, "日本語テキスト", StandardCharsets.UTF_8);
// 2.2 指定字符集读取
String jpText = Files.readString(file, StandardCharsets.UTF_8);

// 3.文件选项配置
// 3.1 组合选项: 不存在则创建 + 追加写入
Files.writeString(file, "新内容",
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);

// 3.2 独占写入 (文件锁定)
Files.writeString(file, "独占内容",
StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW);

// 4. 异常处理最佳实践
try {
String config = Files.readString(Path.of("config.cfg"));
// 处理配置
} catch (NoSuchFileException e) {
System.err.println("配置文件不存在: " + e.getFile());
} catch (AccessDeniedException e) {
System.err.println("无访问权限: " + e.getFile());
} catch (IOException e) {
System.err.println("系统IO错误: " + e.getMessage());
}

高级应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 1. 配置文件热更新
// 监控配置文件变化
WatchService watcher = FileSystems.getDefault().newWatchService();
Path confDir = Path.of("config");
confDir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equals("app.conf")) {
reloadConfig(confDir.resolve("app.conf"));
}
}
key.reset();
}
private void reloadConfig(Path configFile) throws IOException {
String newConfig = Files.readString(configFile);
// 应用新配置...
}

// 2. 文本处理流水线
List<Path> textFiles = Files.list(Path.of("docs"))
.filter(p -> p.toString().endsWith(".txt"))
.toList();
// 并行处理所有文本文件
textFiles.parallelStream()
.map(p -> {
try {
return Files.readString(p);
} catch (IOException e) {
return ""; // 错误处理
}
})
.filter(text -> text.contains("Java 11"))
.forEach(content -> processContent(content));

// 3. 模板文件生成
String template = Files.readString(Path.of("template.html"));
Map<String, String> variables = Map.of(
"${name}", "张三",
"${email}", "zhangsan@example.com"
);
// 替换模板变量
for (Map.Entry<String, String> entry : variables.entrySet()) {
template = template.replace(entry.getKey(), entry.getValue());
}
// 生成最终文件
Files.writeString(Path.of("output.html"), template);

性能优化与注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. 文件大小警告
Path largeFile = Path.of("huge.log");
long size = Files.size(largeFile);
if (size > 100 * 1024 * 1024) { // 超过100MB
// 使用流式处理替代
try (Stream<String> lines = Files.lines(largeFile)) {
lines.filter(line -> line.contains("ERROR"))
.forEach(System.err::println);
}
} else {
String content = Files.readString(largeFile);
// 处理内容...
}

// 2. 字符集最佳实践
// 检测文件BOM头判断编码
public Charset detectCharset(Path file) throws IOException {
byte[] bom = new byte[4];
try (InputStream is = Files.newInputStream(file)) {
is.read(bom);
}
if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) {
return StandardCharsets.UTF_8;
} else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF) {
return StandardCharsets.UTF_16BE;
} else {
return StandardCharsets.UTF_8; // 默认
}
}

3.4 终极示例:完整的配置加载器

Java 11的Files API通过readString()和writeString()方法,用单行代码彻底简化了文件读写操作,自动处理资源关闭、字符编码(默认UTF-8)和缓冲优化,解决了传统IO冗长的样板代码问题,使文本文件操作变得直观高效,适用于配置加载、日志处理等常见场景,虽对超大文件略有性能损耗,但显著提升了开发效率和代码可维护性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ConfigLoader {
private final Path configPath;
private Map<String, String> settings = new HashMap<>();
public ConfigLoader(String filename) {
this.configPath = Path.of("conf", filename);
}
public void load() throws IOException {
String content = Files.readString(configPath, StandardCharsets.UTF_8);
content.lines()
.filter(line -> !line.startsWith("#") && !line.isBlank())
.map(line -> line.split("=", 2))
.forEach(parts -> {
if (parts.length == 2) {
settings.put(parts[0].strip(), parts[1].strip());
}
});
}
public void save() throws IOException {
StringBuilder sb = new StringBuilder("# 自动生成的配置\n");
settings.forEach((k, v) -> sb.append(k).append("=").append(v).append("\n"));
Files.writeString(configPath, sb.toString(),
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.CREATE);
}
}

四、ZGC 与 Epsilon

4.1 痛点问题

传统 GC 的局限性:

  1. STW 停顿时间过长

    • CMS/G1 在 TB 级堆内存下停顿可达秒级

    • 无法满足金融交易、实时系统等低延迟需求

  2. 内存回收效率问题

    • 堆越大,GC 效率越低

    • 传统算法无法线性扩展

  3. 特殊场景缺失

    • 短期任务不需要 GC

    • 性能测试需排除 GC 干扰

4.2 ZGC:亚毫秒级停顿的突破

核心原理

ZGC的核心原理是通过并发着色指针和读屏障技术实现亚毫秒级停顿:

  1. 着色指针在64位地址中嵌入元数据(标记/重映射状态),使GC线程能并发标记对象;

  2. 读屏障在应用线程访问对象时即时修复指针引用,实现堆压缩与标记的并发执行;

  3. 全阶段并发(标记/转移/重定位)仅需<1ms的STW根扫描,使停顿时间与堆大小无关。
    其本质是以空间换时间(保留内存屏障开销)和以CPU换延迟(并发处理),适用于TB级堆内存的低延迟场景。

ZGC 发展路线

  1. Java 15:正式生产可用
  2. Java 17:分代 ZGC (减少内存开销)
  3. Java 21:弹性元空间 (减少 native 内存)

4.3 Epsilon:无操作 GC

Epsilon GC(无操作垃圾收集器)的核心原理是只分配内存,不回收内存,通过彻底消除GC开销来实现极致性能:

  1. 内存分配:使用bump-the-pointer线性分配和TLAB(线程本地缓冲)快速分配对象
  2. 内存不回收:完全跳过标记/清除/压缩等回收阶段,堆耗尽时直接OOM
  3. 零开销:无GC线程、无写屏障、无内存扫描,CPU利用率接近100%

image-20250722124033255

设计哲学:用内存换性能,适合短期任务和性能基准测试,但需确保应用生命周期内堆不耗尽。

4.4 实战应用指南

ZGC 启用与调优:

1
2
3
4
5
6
7
8
9
10
# 基础启用
java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar app.jar

# 高级配置
java -XX:+UseZGC \
-Xmx16g \ # 堆大小
-XX:ConcGCThreads=4 \ # 并发GC线程
-XX:ZAllocationSpikeTolerance=5 \ # 分配尖峰容忍度
-Xlog:gc*:file=gc.log \ # GC日志
-jar trading-system.jar

Epsilon 使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 性能基准测试(排除GC干扰)
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseEpsilonGC \
-Xmx1g \
-jar benchmark.jar

# 2. 短期任务(如AWS Lambda)
java -XX:+UseEpsilonGC \
-Xmx128m \
-jar lambda-function.jar

# 3. 内存行为测试
java -XX:+UseEpsilonGC \
-XX:+HeapDumpOnOutOfMemoryError \
-Xmx100m \
-jar memory-test.jar

4.5 总结

ZGC、Epsilon和G1的选型核心在于权衡延迟、内存与吞吐需求: ZGC凭借并发着色指针实现亚毫秒级停顿,适合TB级堆内存的低延迟场景(如金融交易);Epsilon彻底放弃垃圾回收,以零GC开销服务于短期任务和性能测试;而G1作为Java默认收集器,通过分Region混合回收平衡吞吐与延迟(200ms内),是通用服务的最佳选择。三者分别覆盖了极致延迟、无GC干扰和通用稳定的三大技术象限,开发者需根据堆大小、任务生命周期和延迟要求精准匹配。


Java17 新特性

原文链接:https://cloud.tencent.com/developer/article/2475140

Java 17是Java编程语言的最新版本,于2021年9月14日发布。以下是Java 17的一些新特性:

  1. Sealed类和接口:Sealed类和接口限制了继承和实现的范围,在编译时提供更强的封装性。
  2. Pattern匹配:Pattern匹配简化了对实例进行类型检查和转换的流程,使代码更加简洁和易读。
  3. 垃圾收集器(G1)的改进:Java 17对G1垃圾收集器进行了改进,包括改进了内存分配、暂停时间优化和垃圾回收性能等方面。
  4. Oracle数据库连接的改进:Java 17引入了一个新的API,使开发人员可以更轻松地与Oracle数据库进行连接和交互。
  5. 嵌套的JVM标签:Java 17支持在字节码层面上为代码添加标签,以便在运行时进行检查和验证。
  6. 私有嵌套接口:Java 17允许在接口内部定义私有嵌套接口,以实现更好的封装性和模块化。
  7. 静态成员类型引用:Java 17允许使用静态成员类型引用,以简化代码并提高可读性。
  8. UNIX域套接字通道:Java 17引入了对UNIX域套接字通道的支持,允许Java程序通过UNIX套接字与本地进程进行通信。

原文链接:https://blog.csdn.net/m0_53548081/article/details/147604067

1. 密封类(Sealed Classes)

密封类允许开发者精确控制哪些类可以继承或实现一个类或接口,增强了Java的封装性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 密封接口示例
public sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
}

// 允许的实现类
// 圆
public final class Circle implements Shape {
private final double radius;

public Circle(double radius) {
this.radius = radius;
}

@Override
public double area() {
return Math.PI * radius * radius;
}
}
// 矩形
public final class Rectangle implements Shape {
private final double width;
private final double height;

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

@Override
public double area() {
return width * height;
}
}
// 三角形
public final class Triangle implements Shape {
private final double base;
private final double height;

public Triangle(double base, double height) {
this.base = base;
this.height = height;
}

@Override
public double area() {
return 0.5 * base * height;
}
}

// 密封类的优势在于模式匹配时的穷尽性保证:
public static String getShapeDescription(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,面积: " + c.area();
case Rectangle r -> "矩形,面积: " + r.area();
case Triangle t -> "三角形,面积: " + t.area();
// 编译器知道所有可能的子类,不需要default分支
};
}

2.记录类(Records)

Records为创建不可变数据传输对象(DTO)提供了简洁的语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 传统POJO
public class PersonTraditional {
private final String name;
private final int age;

public PersonTraditional(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersonTraditional that = (PersonTraditional) o;
return age == that.age && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "PersonTraditional{name='" + name + "', age=" + age + '}';
}
}

// 使用Record简化
public record Person(String name, int age) {
// 编译器自动生成构造器、getters、equals、hashCode和toString
// 可以添加额外的方法
public boolean isAdult() {
return age >= 18;
}
// 可以自定义规范构造器
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}

// 使用Record的业务案例
public class RecordsExample {
public static void main(String[] args) {
// 创建记录实例
Person person = new Person("张三", 30);
// 获取属性
System.out.println("姓名: " + person.name());
System.out.println("年龄: " + person.age());
// 使用自定义方法
System.out.println("是否成年: " + person.isAdult());
// 解构记录
var userInfo = getUserInfo();
processUserData(userInfo.username(), userInfo.email());
}

private static UserInfo getUserInfo() {
return new UserInfo("admin", "admin@example.com");
}

private static void processUserData(String username, String email) {
System.out.println("处理用户: " + username + ", 邮箱: " + email);
}
}
record UserInfo(String username, String email) {}

3. 模式匹配for instanceof

Java 17改进了instanceof运算符,使其能够同时进行类型检查和类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 传统写法
public String getDescriptionTraditional(Object obj) {
if (obj instanceof String) {
String s = (String) obj;
return "字符串,长度: " + s.length();
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
return "整数: " + i;
} else {
return "未知类型";
}
}

// 使用模式匹配for instanceof
public String getDescription(Object obj) {
if (obj instanceof String s) {
return "字符串,长度: " + s.length();
} else if (obj instanceof Integer i) {
return "整数: " + i;
} else {
return "未知类型";
}
}

// 与Records结合使用
public void processShape(Object obj) {
if (obj instanceof Rectangle r) {
System.out.println("矩形面积: " + r.area());
} else if (obj instanceof Circle c && c.area() > 100) {
System.out.println("大型圆形,面积: " + c.area());
}
}

4. Switch表达式增强

Java 17继承了Java 14引入的Switch表达式,与模式匹配结合使用更加强大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public String getTypeDescription(Object obj) {
return switch (obj) {
case String s -> "字符串,内容: " + s;
case Integer i -> "整数: " + i;
case Long l -> "长整型: " + l;
case Double d -> "双精度: " + d;
case null -> "空值";
default -> "其他类型";
};
}
// 带条件的模式匹配
public String analyzePerson(Person person) {
return switch (person) {
case Person p when p.age() < 18 -> p.name() + " 是未成年人";
case Person p when p.age() >= 18 && p.age() < 65 -> p.name() + " 是成年人";
case Person p -> p.name() + " 是老年人";
};
}

5. 文本块(Text Blocks)

文本块提供了更好的多行字符串表示方式,特别适合SQL、HTML等内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class TextBlocksExample {
public static void main(String[] args) {
// 传统多行字符串
String oldHtml = "<html>\n" +
" <body>\n" +
" <p>Hello, World!</p>\n" +
" </body>\n" +
"</html>";

// 使用文本块
String html = """
<html>
<body>
<p>Hello, World!</p>
</body>
</html>
""";

// SQL查询示例
String query = """
SELECT id, name, email
FROM users
WHERE status = 'active'
AND last_login > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY name ASC
""";

// JSON示例
String json = """
{
"name": "张三",
"age": 30,
"address": {
"city": "北京",
"district": "海淀"
},
"skills": ["Java", "Spring", "Kubernetes"]
}
""";
}
}

6. 新的随机数生成器API

Java 17引入了强大的随机数生成器API,提供了更多算法选择和更好的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;

public class RandomGeneratorExample {
public static void main(String[] args) {
// 获取默认实现
RandomGenerator defaultRandom = RandomGenerator.getDefault();
// 生成随机数
System.out.println("随机整数: " + defaultRandom.nextInt(100));
// 使用特定算法
RandomGenerator xoroshiro = RandomGeneratorFactory.of("Xoroshiro128PlusPlus")
.create(42); // 使用种子值
// 生成随机序列
xoroshiro.ints(10, 1, 100)
.forEach(n -> System.out.print(n + " "));
// 列出所有可用算法
System.out.println("\n可用随机数生成器算法:");
RandomGeneratorFactory.all()
.map(factory -> factory.name())
.sorted()
.forEach(System.out::println);
}
}

7. 强封装JDK内部API

Java 17进一步强化了JDK内部API的封装,这需要开发者更加关注公共API的使用。

1
2
3
4
5
6
7
8
9
// 使用反射访问内部类可能会失败
try {
// 这可能在Java 17中无法正常工作
Class<?> sunClass = Class.forName("sun.misc.Unsafe");
// ...
} catch (ClassNotFoundException | IllegalAccessException e) {
System.out.println("无法访问内部API: " + e.getMessage());
// 使用替代公共API
}

8. 实际业务应用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 构建现代商品管理系统
// 产品层次结构
public sealed interface Product permits Electronics, Clothing, Book {
String getId();
String getName();
double getPrice();
}
public record Electronics(String id, String name, double price,
String brand, int warrantyMonths) implements Product {}
public record Clothing(String id, String name, double price,
String size, String material) implements Product {}
public record Book(String id, String name, double price,
String author, String isbn) implements Product {}

// 产品处理
public class ProductProcessor {
public String generateProductDescription(Product product) {
return switch (product) {
case Electronics e -> String.format("%s - %s, 保修期: %d个月",
e.name(), e.brand(), e.warrantyMonths());
case Clothing c -> String.format("%s - 尺码: %s, 材质: %s",
c.name(), c.size(), c.material());
case Book b -> String.format("%s - 作者: %s, ISBN: %s",
b.name(), b.author(), b.isbn());
};
}
public double calculateTax(Product product) {
return switch (product) {
case Electronics e when e.price() > 10000 -> e.price() * 0.2;
case Electronics e -> e.price() * 0.15;
case Clothing c -> c.price() * 0.1;
case Book b -> b.price() * 0.05;
};
}
}

// 购物车功能
public class ShoppingCart {
private final List<Product> items = new ArrayList<>();

public void addItem(Product product) {
items.add(product);
}
public double calculateTotal() {
return items.stream().mapToDouble(Product::getPrice).sum();
}
public double calculateTotalWithTax() {
ProductProcessor processor = new ProductProcessor();
return items.stream().mapToDouble(p -> p.getPrice() + processor.calculateTax(p)).sum();
}
public String getReceipt() {
ProductProcessor processor = new ProductProcessor();
StringBuilder builder = new StringBuilder();
builder.append("""
===== 购物小票 =====
日期: %s
商品列表:
""".formatted(LocalDate.now()));
items.forEach(item ->
builder.append("- %s: %.2f 元 (税: %.2f 元)\n"
.formatted(item.getName(), item.getPrice(), processor.calculateTax(item)))
);
builder.append("""
----------------
总计: %.2f 元
含税总价: %.2f 元
================
""".formatted(calculateTotal(), calculateTotalWithTax()));
return builder.toString();
}
}

// 测试商品管理系统
public class ProductSystemDemo {
public static void main(String[] args) {
// 创建几件商品
Product laptop = new Electronics("E001", "MacBook Pro", 12999.0, "Apple", 24);
Product tshirt = new Clothing("C001", "纯棉T恤", 199.0, "XL", "棉");
Product javaBook = new Book("B001", "Java编程思想", 108.0, "Bruce Eckel", "978-7111213826");
// 添加到购物车
ShoppingCart cart = new ShoppingCart();
cart.addItem(laptop);
cart.addItem(tshirt);
cart.addItem(javaBook);
// 打印购物小票
System.out.println(cart.getReceipt());
// 直接使用Switch表达式进行产品分析
Product randomProduct = getRandomProduct(laptop, tshirt, javaBook);
String analysisResult = switch (randomProduct) {
case Electronics e when e.price() > 10000 -> "高端电子产品";
case Electronics e -> "普通电子产品";
case Clothing c when "棉".equals(c.material()) -> "纯棉服装";
case Clothing c -> "其他材质服装";
case Book b -> "图书类产品";
};
System.out.println("随机商品分析: " + analysisResult);
}
private static Product getRandomProduct(Product... products) {
return products[RandomGenerator.getDefault().nextInt(products.length)];
}
}

9. 总结

Java 17的新特性为开发者提供了更多优雅高效的编程工具:

  1. 密封类:增强了类型系统,提供了更好的封装性和编译时安全性
  2. 记录类:极大简化了数据传输对象的编写
  3. **模式匹配for instanceof**:简化了类型检查和转换操作
  4. Switch表达式增强:使分支逻辑更简洁清晰
  5. 文本块:大幅改进了多行字符串的处理
  6. 新的随机数API:提供了更丰富的随机数生成工具
  7. 强封装JDK内部API:鼓励使用公共API,提高代码稳定性

这些新特性不仅提高了代码的简洁性和可读性,还增强了Java语言的表达能力和安全性。结合实际业务场景使用这些特性,可以编写出更优雅、更健壮的Java应用程序。