0%

java新特性

Java新特性

img

编码效率

jshell

简介

JShell这个特性,是在JDK 9正式发布的。JShell API和工具提供了一种在 JShell 状态下交互式评估 Java 编程语言的声明、语句和表达式的方法。JShell 的状态包括不断发展的代码和执行状态。为了便于快速调查和编码,语句和表达式不需要出现在方法中,变量和方法也不需要出现在类中。

使用jshell

jshell.exe
| 欢迎使用 JShell – 版本 17.0.10
| 要大致了解该版本, 请键入: /help intro

jshell> System.out.println(“Hello”)
Hello

jshell> int[] nums = new int[]{1, 2, 3, 4}
nums ==> int[4] { 1, 2, 3, 4 }

jshell> for (int num: nums) {System.out.println(num);}
1
2
3
4

文字块

简介

文字块这个特性,首先在JDK 13中以预览版的形式发布。在JDK 14中,改进的文字块再次以预览版的形式发布。最后,文字块在JDK 15正式发布。

文字块的概念很简单,它是一个由多行文字构成的字符串。文字块尝试消除换行、连接符、转义字符的影响,使得文字对齐和必要的占位符更加清晰,从而简化多行文字字符串的表达。

使用文字块

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 TextBlocks {
public static void main(String[] args) {
String stringBlock =
"<!DOCTYPE html>\n" +
"<html>\n" +
" <body>\n" +
" <h1>\"Hello World!\"</h1>\n" +
" </body>\n" +
"</html>\n";

String textBlock = """
<!DOCTYPE html> \s
<html>
<body>
<h1>"Hello \
World!"</h1> \s
</body>
</html>
""";
System.out.println(
"Does the text block equal to the regular string? " +
stringBlock.equals(textBlock));
System.out.println(
"Does the text block refer to the regular string? " +
(stringBlock == textBlock));
}
}
  • 保留尾部空格
    • 文字块在编译期间,会删除共享的前导空格和尾部空格。使用\s,可保留上一个字符到\s之间的空格
  • 换行符
    • 当一行过长时,可以使用\进行换行表示

档案类

简介

档案类这个特性,首先在JDK 14中以预览版的形式发布。在JDK 15中,改进的档案类再次以预览版的形式发布。最后,档案类在JDK 16正式发布

Java档案类是用来表示不可变数据的透明载体,

不可变数据

  • Java档案类不支持扩展子句,用户不能定制它的父类。隐含的,它的父类是java.lang.Record。父类不能定制,也就意味着我们不能通过修改父类来影响Java档案的行为。
  • Java档案类是个终极(final)类,不支持子类,也不能是抽象类。没有子类,也就意味着我们不能通过修改子类来改变Java档案的行为。
  • Java档案类声明的变量是不可变的变量。这就是我们前面反复强调的,一旦实例化就不能再修改的关键所在。
  • Java档案类不能声明可变的变量,也不能支持实例初始化的方法。这就保证了,我们只能使用档案类形式的构造方法,避免额外的初始化对可变性的影响。
  • Java档案类不能声明本地(native)方法。如果允许了本地方法,也就意味着打开了修改不可变变量的后门。

透明的载体

档案类在内部缺省了以下方法

  • 构造方法
  • equals方法
  • hashCode方法
  • toString方法
  • 不可变数据的读取方法

档案类的模式有点像String类型行为,定义不可变数据类型。并且其比较时需要重写equals方法、hashCode方法方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建档案类
public record Circle(double radius) implements Shape {
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}



public class Main {
public static void main(String[] args) {
Circle circle = new Circle(10.0);
Circle circle2 = new Circle(10.0);
System.out.println(circle2.equals(circle)); // true 缺省的equals 一一比较了成员变量
System.out.println(circle.hashCode() + " : " + circle2.hashCode()); // 相同 hashCode也是成员变量的
System.out.println(circle.radius());
}
}

封闭类

简介

封闭类这个特性,首先在JDK 15中以预览版的形式发布。在JDK 16中,改进的封闭类再次以预览版的形式发布。最后,封闭类在JDK 17正式发布。

封闭类的声明使用 sealed 类修饰符,然后在所有的 extends 和 implements 语句之后,使用 permits 指定允许扩展该封闭类的子类。

封闭类控制了子类的范围,控制了子类的扩展范围。通过把可扩展性的限制放在可以预测和控制的范围内,封闭类和封闭接口打开了全开放和全封闭两个极端之间的中间地带,为接口设计和实现提供了新的可能性。

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 abstract sealed class Shape {  // 封闭类
public final String id;

public Shape(String id) {
this.id = id;
}

public abstract double area();

public static non-sealed class Circle extends Shape { // 解封类
// snipped
}

public static sealed class Square extends Shape { // 延续封闭类
// snipped
}

public static final class ColoredSquare extends Square { // 终极类
// snipped
}

public static class ColoredCircle extends Circle { // 子类
// snipped
}
}

许可类的声明需要满足下面的三个条件:

  • 许可类必须和封闭类处于同一模块(module)或者包空间(package)里,也就是说,在编译的时候,封闭类必须可以访问它的许可类;
  • 许可类必须是封闭类的直接扩展类;
  • 许可类必须声明是否继续保持封闭:
    • 许可类可以声明为终极类(final),从而关闭扩展性;
    • 许可类可以声明为封闭类(sealed),从而延续受限制的扩展性;
    • 许可类可以声明为解封类(non-sealed), 从而支持不受限制的扩展性。

需要注意的是,**由于许可类必须是封闭类的直接扩展,因此许可类不具备传递性。**也就是说,上面的例子中,ColoredSquare 是 Square 的许可类,但不是 Shape 的许可类。

模式匹配

简介

Java的模式匹配是一个新型的、而且还在持续快速演进的领域。类型匹配是模式匹配的一个规范。类型匹配这个特性,首先在JDK 14中以预览版的形式发布。在JDK 15中,改进的类型匹配再次以预览版的形式发布。最后,类型匹配在JDK 16正式发布。

图片

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
public sealed interface Shape
permits Shape.Circle, Shape.Rectangle, Shape.Square {
Shape.Rectangle rect = null; // field variable

record Circle(double radius) implements Shape {
// blank
}

record Square(double side) implements Shape {
// blank
}

record Rectangle(double length, double width) implements Shape {
// blank
}

static void main(String[] args) {
Shape shape = new Shape.Rectangle(10, 10);
System.out.println("It should be ture that " + shape +
" is a square: " + isSquareImplE(shape));

System.out.println();

shape = new Shape.Circle(10);
System.out.println("It cannot be ture that " + shape +
" is a square: " + (!isSquareImplE(shape)));
}

static boolean isSquare(Shape shape) {
if (shape instanceof Rectangle rect) {
// Field rect is shadowed, local rect is in scope
System.out.println(
"This should be the local rect: " +
rect.equals(shape));
return (rect.length == rect.width);
}

// Field rect is in scope, local rect is not in scope here
System.out.println(
"This should be the field rect: " + (rect == null));
return (shape instanceof Square);
}

static boolean isSquareImplB(Shape shape) {
if (!(shape instanceof Rectangle rect)) {
// rect is not in scope here
return shape instanceof Square;
}

// rect is in scope
return rect.length() == rect.width();
}

static boolean isSquareImplC(Shape shape) {
return shape instanceof Square || // rect is not in scope here
(shape instanceof Rectangle rect &&
rect.length() == rect.width()); // rect is in scope here
}

static boolean isSquareImplD(Shape shape) {
return shape instanceof Square || // rect is not in scope here
(shape instanceof Rectangle rect ||
rect.length() == rect.width()); // rect is not in scope here
}

static boolean isSquareImplE(Shape shape) {
return shape instanceof Square | // rect is not in scope here
(shape instanceof Rectangle rect &
rect.length() == rect.width()); // rect is in scope here
}
}

switch表达式

简介

switch表达式这个特性,首先在JDK 12中以预览版的形式发布。在JDK 13中,改进的switch表达式再次以预览版的形式发布。最后,switch表达式在JDK 14正式发布。

假设现在使用switch语句获取月份的天数

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
class DaysInMonth {
public static void main(String[] args) {
Calendar today = Calendar.getInstance();
int month = today.get(Calendar.MONTH);
int year = today.get(Calendar.YEAR);

int daysInMonth;
switch (month) {
case Calendar.JANUARY:
case Calendar.MARCH:
case Calendar.MAY:
case Calendar.JULY:
case Calendar.AUGUST:
case Calendar.OCTOBER:
case Calendar.DECEMBER:
daysInMonth = 31;
break;
case Calendar.APRIL:
case Calendar.JUNE:
case Calendar.SEPTEMBER:
case Calendar.NOVEMBER:
daysInMonth = 30;
break;
case Calendar.FEBRUARY:
if (((year % 4 == 0) && !(year % 100 == 0))
|| (year % 400 == 0)) {
daysInMonth = 29;
} else {
daysInMonth = 28;
}
break;
default:
throw new RuntimeException(
"Calendar in JDK does not work");
}

System.out.println(
"There are " + daysInMonth + " days in this month.");
}
}

使用switch表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DaysInMonth {

public static void main(String[] args) {
Calendar instance = Calendar.getInstance();
int month = instance.get(Calendar.MONTH);
int year = instance.get(Calendar.YEAR);
int daysInMonth = switch (month) {
case Calendar.JANUARY, Calendar.MARCH, Calendar.MAY, Calendar.JULY,
Calendar.AUGUST, Calendar.OCTOBER, Calendar.DECEMBER -> 31;
case Calendar.APRIL, Calendar.JUNE, Calendar.SEPTEMBER, Calendar.NOVEMBER -> 30;
case Calendar.FEBRUARY -> {
if (((year % 4 == 0) && !(year % 100 == 0))
|| (year % 400 == 0)) {
yield 29;
}
yield 28;
}
default -> throw new RuntimeException("Calendar in JDK does not work");
};
System.out.println(daysInMonth);
}

总结

图片

  • break语句只能出现在switch语句里,不能出现在switch表达式里;
  • yield语句只能出现在switch表达式里,不能出现在switch语句里;
  • switch表达式需要穷举出所有的情景,而switch语句不需要情景穷举;
  • 使用冒号标识符的swtich形式,支持情景间的fall-through;而使用箭头标识符的swtich形式不支持fall-through;
  • 使用箭头标识符的swtich形式,一个case语句支持多个情景;而使用冒号标识符的swtich形式不支持多情景的case语句。

switch匹配

简介

switch的模式匹配这个特性,在JDK 17中以预览版的形式发布。

1
2
3
4
5
6
public static boolean isSquare(Shape shape) {
return switch (shape) {
case null, Shape.Circle c -> false;
case Shape.Square s -> true;
};
}

代码性能

错误码

异常状况的处理会让代码的效率变低,所以我们不应该使用异常机制来处理正常的状况

同一段代码,一个不抛出异常,一个抛出异常。它们之间的执行效率可能差距千倍

以下使用错误码的案例,举例了使用档案类和记录类,使用switch匹配和模式匹配的方式,避免异常抛出的案例。以提高代码执行效率

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
public sealed abstract class Digest {
private static final class SHA256 extends Digest {
@Override
byte[] digest(byte[] message) {
// snipped
return "SHA-256".getBytes(StandardCharsets.UTF_8);
}
}

private static final class SHA512 extends Digest {
@Override
byte[] digest(byte[] message) {
// snipped
return "SHA512".getBytes(StandardCharsets.UTF_8);
}
}

public static Returned<Digest> of(String algorithm) {
return switch (algorithm) {
case "SHA-256" -> new Returned.ReturnValue<>(new SHA256());
case "SHA-512" -> new Returned.ReturnValue<>(new SHA512());
default -> new Returned.ErrorCode(-1);
};
}

abstract byte[] digest(byte[] message);
}

public sealed interface Returned<T> permits Returned.ReturnValue, Returned.ErrorCode {

record ReturnValue<T>(T returnValue) implements Returned<T> {

}

record ErrorCode(int errorCode) implements Returned {

}

}
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 UseCase {

public static void main(String[] args) {
Returned<Digest> rt = Digest.of("SHA-256");
if (rt instanceof Returned.ReturnValue<Digest> rv) {
Digest d = rv.returnValue();
System.out.println(new String(d.digest("Hello, world!".getBytes())));
} else if (rt instanceof Returned.ErrorCode ec) {
System.out.println("Failed to get instance of SHA-256");
}
}
}
public class UseCase {
public static void main(String[] args) {
Returned<Digest> rt = Digest.of("SHA-256");
switch (rt) {
case Returned.ReturnValue rv -> {
Digest d = (Digest) rv.returnValue();
d.digest("Hello, world!".getBytes());
}
case Returned.ErrorCode ec ->
System.out.println("Failed to get instance of SHA-256");
}
}
}

当然使用错误码并不是一个完美的解决方案,错误码不像异常那样拥有调用栈信息,不利于问题定位

外部内存

Java的外部内存接口这个新特性,现在还在孵化期。。像TensorFlow、 Ignite、 Flink以及Netty这样的类库,往往对性能有着偏执的追求。为了避免Java垃圾收集器不可预测的行为以及额外的性能开销,这些产品一般倾向于使用JVM之外的内存来存储和管理数据。这样的数据,就是我们常说的堆外数据(off-heap data)。

ByteBuffer

ByteBuffer是异步编程和非阻塞编程的核心类,几乎所有的Java异步模式或者非阻塞模式的代码,都要直接或者间接地使用ByteBuffer来管理数据。但是ByteBuffer有两个明显的缺陷

  • 没有资源释放的接口。一旦一个ByteBuffer实例化,它占用了内存的释放,就会完全依赖JVM的垃圾回收机制。依赖于垃圾回收机制的资源回收方式,并不能满足像Netty这样的类库的理想需求。
  • 存储空间尺寸的限制。ByteBuffer的存储空间的大小,是使用Java的整数来表示的。所以,它的存储空间,最多只有2G。

外部内存接口沿袭了ByteBuffer的设计思路,但是使用了全新的接口布局。

1
2
3
4
5
6
try (ResourceScope scope = ResourceScope.newConfinedScope()) {  // 语法糖,try-with-resource及时释放内存
MemorySegment segment = MemorySegment.allocateNative(4, scope); // 定义申请和模拟了一段连续的内存区域
for (int i = 0; i < 4; i++) {
MemoryAccess.setByteAtOffset(segment, i, (byte)'A'); // 放置字母A
}
}

外部函数

Java的外部函数接口这个新特性,代替了原有的JNI(Java Native Interface)。并在JNI的基础上完善了动态库无法跨平台、JNI脱离JVM语言安全机制的问题

JNI实现

像Java或者Go这样的通用编程语言,都需要和其他的编程语言或者环境打交道,比如操作系统或者C语言。Java是通过Java本地接口(Java Native Interface, JNI)来支持这样的做法的。

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
// 1,编写java程序
public class HelloWorld {
static {
System.loadLibrary("helloWorld");
}

public static void main(String[] args) {
new HelloWorld().sayHello();
}

private native void sayHello();
}
// 2, 实现C语言函数
#include "jni.h"
#include "HelloWorld.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject jObj) {
printf("Hello World!\n");
}

// 3,把C语言的实现编译、链接放到它的动态库里
$ gcc -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/darwin \
-dynamiclib HelloWorld.c -o libhelloWorld.dylib

// 4,运行
java -cp . -Djava.library.path=. HelloWorld

外部函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

import java.lang.invoke.MethodType;
import jdk.incubator.foreign.*;

public class HelloWorld {
public static void main(String[] args) throws Throwable {
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
CLinker cLinker = CLinker.getInstance();
MemorySegment helloWorld =
CLinker.toCString("Hello, world!\n", scope);
MethodHandle cPrintf = cLinker.downcallHandle(
CLinker.systemLookup().lookup("printf").get(),
MethodType.methodType(int.class, MemoryAddress.class),
FunctionDescriptor.of(CLinker.C_INT, CLinker.C_POINTER));
cPrintf.invoke(helloWorld.address());
}
}
}

禁止空指针

在C语言和Java语言里,存在着大量的空指针。不管我们怎么努力,也不管我们经验多么丰富,总是会时不时地就忘了检查空指针。而忘了检查这样的小错误,很可能就蔓延成严重的事故。所以,空指针发明者称它是一个价值10亿美元的错误。

在jdk17中对NullPointerException做了优化,在msg中包含调用者信息,减少了问题排查难度

1
2
3
4
5
6
7
jshell> String a = null
a ==> null
| 已创建 变量 a : String

jshell> a.equals("b")
| 异常错误 java.lang.NullPointerException:Cannot invoke "String.equals(Object)" because "REPL.$JShell$13.a" is null
| at (#4:1)

Optional

在jdk8中可以使用Optional.isPresent()方法判断null,但是isPresent不是一个强制检查的方法,开发还是可以直接使用.get()方法获取值

1
2
3
4
5
6
7
8
private static boolean hasMiddleName(
FullName fullName, String middleName) {
if (fullName.middleName().isPresent()) {
return fullName.middleName().get().equals(middleName);
}

return middleName == null;
}

封闭类

这种使用了封闭类和模式匹配的设计,极大地压缩了开发者的自由度,强制要求开发者的代码必须执行空指针的检查,只有这样才能编写下一步的代码。 这种看似放弃了灵活性的设计,恰恰把开发者从低级易犯的错误中解救了出来。不论是对写代码的开发者,还是对读代码的开发者来说,这都是一件好事。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public sealed interface Returned<T> {
Returned.Undefined UNDEFINED = new Undefined();

record ReturnValue<T>(T returnValue) implements Returned {
}

record Undefined() implements Returned {
}
}

private static boolean hasMiddleName(FullName fullName, String middleName) {
return switch (fullName.middleName()) {
case Returned.Undefined undefined -> false;
case Returned.ReturnValue rv -> {
String returnedMiddleName = (String)rv.returnValue();
yield returnedMiddleName.equals(middleName);
}
};
}

维护维度

模块化

Java 平台模块系统(Java Platform Module System,JPMS),模块化可以帮助各级开发人员在构建、维护和演进软件系统时提高工作效率。软件系统规模越大,我们越需要这样的工程技术。

缺失的访问控制

从这个列表看,Java 语言访问修饰符似乎覆盖了所有的可能性,这好像是一个完备的定义。遗憾的是,Java 语言访问修饰符遗漏了很重要的一种情况,那就是 Java 包之间的关系。Java 包之间的关系,并不是要么全开放,要么全封闭这么简单。

类似于继承类之间的私密通道,Java 包之间也有这种类似私密通道的需求。比如说,我们在 JDK 的标准类库里,可以看到像 java.net 这样的放置公开接口的包,也可以看到像 sun.net 这样的放置实现代码的包。

img

模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义加密接口标准的包
module jus.crypto {
// 当前模块向外暴露加密的公共接口标准
exports co.ivi.jus.crypto;
// 当前模块直接使用了 DigestManager 定义的服务接口
uses co.ivi.jus.crypto.DigestManager;
}

// 实现了加密接口标准的包
module jus.crypto.impl {
// 当前包依赖了加密接口标准
requires jus.crypto;

// DigestManagerImpl实现了jus.crypto的服务接口
provides co.ivi.jus.crypto.DigestManager with co.ivi.jus.impl.DigestManagerImpl;
}

// 使用加密的包
module jus.crypto.use {
// 当前包依赖了加密接口标准
requires jus.crypto;
}

https://www.cnblogs.com/IcanFixIt/p/6947763.html

文章作者:xpp011

发布时间:2024年11月06日 - 22:11

原始链接:http://xpp011.cn/2024/11/06/4503252.html

许可协议: 转载请保留原文链接及作者。