JDK11新特性
OpenJDK的各种实现
这段时间比对了不少 OpenJDK 的发行版,最后找到Liberica JDK。
https://bell-sw.com/pages/java-11.0.6/
对一般用户来说,Liberica JDK 应该是最友好的 OpenJDK 发行版。
Liberica JDK 默认捆绑了 JavaFX (AdoptOpenjdk 和 OracleJDK 都没有);
提供 Java 13 的构建(Zulu 13 没有捆绑 OpenJFX 的版本,只有 Zulu 11 有);
为 Linux 与 Windows 提供 32 位构建(OracleJDK 不提供 32 位版本,Zulu 只为 Windows 提供,而且只有 Zulu 11 是提供捆绑 OpenJFX 版本的);
Windows 安装包自动配置环境变量,并且自动关联 jar 打开方式(Zulu 和 OracleJDK 好像不会配置环境变量);
国内直连下载速度很快(AdoptOpenJDK 出来挨打)。
JavaFX是可以当依赖库用,但是一来是不方便,二来你会发现…… 实际上托管的仓库是只有x86_64版的,ARM和X86都不支持, 想在这些地方跑还是得自己找其他构建。
Liberica JDK带各种指令集的JavaFX,是一个不错的OpenJDK实现。
wget -q -O - https://download.bell-sw.com/pki/GPG-KEY-bellsoft | sudo apt-key add - echo "deb [arch=amd64] https://apt.bell-sw.com/ stable main" | sudo tee /etc/apt/sources.list.d/bellsoft.list sudo apt-get update sudo apt-get install bellsoft-java8 sudo apt-get install bellsoft-java11
Java 8
HTTP2.0
需要加载alpn包:https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn
日期时间 API
java.util.Date
为可变类型,以及SimpleDateFormat
非线程安全的缺点。
Java 8 推出了全新的日期时间API,
java.time
包下的所有类都是不可变类型而且线程安全。
与旧API的对应关系
Java.time ISO Calendar | Java.util Calendar |
---|---|
Instant | Date |
LocalDate | Calendar |
LocalTime | |
LocalDateTime | |
ZonedDateTime | |
OffsetDateTime, OffsetTime | Calendar |
Zoneld, ZoneOffset, ZooneRules | TimeZone |
Week Starts on Monday(1 ... 7) | Week Starts on Monday(1 ... 7) |
enum MONDAY, TUESDAY, ... SUNDAY | int values MONDAY, TUESDAY, ... SUNDAY |
12 Month (1 ... 12) | 12 Month (1 ... 12) |
enum JANUARY, FEBRUARY, ... DECEMBER | int values JANUARY, FEBRUARY, ... DECEMBER |
Java 9
集合增强
从Java 9 开始,jdk里面就为集合(List、Set、Map)增加了of和copyOf方法。 它们用来创建不可变集合。
- of() @since 9
- copyOf() @since 10
示例一:
var list = List.of("Java", "Python", "C"); //不可变集合 var copy = List.copyOf(list); //copyOf判断是否是不可变集合类型,如果是直接返回 System.out.println(list == copy); // true var list = new ArrayList<String>(); // 这里返回正常的集合 var copy = List.copyOf(list); // 这里返回一个不可变集合 System.out.println(list == copy); // false
示例二:
var set = Set.of("Java", "Python", "C"); var copy = Set.copyOf(set); System.out.println(set == copy); // true var set1 = new HashSet<String>(); var copy1 = List.copyOf(set1); System.out.println(set1 == copy1); // false
示例三:
var map = Map.of("Java", 1, "Python", 2, "C", 3); var copy = Map.copyOf(map); System.out.println(map == copy); // true var map1 = new HashMap<String, Integer>(); var copy1 = Map.copyOf(map1); System.out.println(map1 == copy1); // false
注意:
-
使用
of
和copyOf
创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作, 不然会报java.lang.UnsupportedOperationException
异常, -
使用
Set.of()
不能出现重复元素、Map.of()
不能出现重复key, 否则回报java.lang.IllegalArgumentException
。 -
of(...)
方法重载了 0 ~ 10 个参数的不同方法 。Map 接口如果超过 10 个参数, 可以使用ofEntries(...)
方法。
接口支持私有方法和私有静态方法
接口中允许 | Java 8 | Java 9 |
---|---|---|
常量 | ✅ | ✅ |
抽象方法 | ✅ | ✅ |
默认方法 | ✅ | ✅ |
静态方法 | ✅ | ✅ |
私有方法 | ❌ | ✅ |
私有静态方法 | ❌ | ✅ |
例如:
interface Test{ String fields = "interface field"; public abstract void abstractMethods(); default void defaultMethods() { System.out.println("default Method"); staticMethods(); privateMethods(); privateStaticMethods(); } static void staticMethods() { System.out.println("static Method"); } private void privateMethods() { System.out.println("private Method"); } private static void privateStaticMethods() { System.out.println("private Static Method"); } } /* 接口实现类 */ public class TestImpl implements Test { @Override public void abstractMethods() { System.out.println("abstract Method"); } } /* 测试类 */ public class Demo { public static void main(String[] args) { TestImpl testImpl = new TestImpl(); System.out.println(testImpl.fields); testImpl.abstractMethods(); testImpl.defaultMethods(); } } // 输出: // interface field // abstract Method // default Method // static Method // private Method // private Static Method
Stream增强
Stream是Java 8 中的特性,在Java 9 中为其新增了4个方法:
-
ofNullable(T t)
此方法可以接收null来创建一个空流
Stream.of(null); //以前 报错 Stream.ofNullable(null); // 现在
-
takeWhile(Predicate<? super T> predicate)
此方法根据Predicate接口来判断如果为true就 取出 来生成一个新的流,只要碰到false就终止,不管后边的元素是否符合条件。
Stream<Integer> integerStream = Stream.of(6, 10, 11, 15, 20); Stream<Integer> takeWhile = integerStream.takeWhile(t -> t % 2 == 0); takeWhile.forEach(System.out::println); // 6,10
-
dropWhile(Predicate<? super T> predicate)
此方法根据Predicate接口来判断如果为true就 丢弃 来生成一个新的流,只要碰到false就终止,不管后边的元素是否符合条件。
Stream<Integer> integerStream = Stream.of(6, 10, 11, 15, 20); Stream<Integer> takeWhile = integerStream.dropWhile(t -> t % 2 == 0); takeWhile.forEach(System.out::println); //11,15,20
-
iterate重载
以前使用iterate方法生成无限流需要配合limit进行截断
Stream<Integer> limit = Stream.iterate(1, i -> i + 1).limit(5); limit.forEach(System.out::println); //1,2,3,4,5
现在重载后这个方法增加了个判断参数
Stream<Integer> iterate = Stream.iterate(1, i -> i <= 5, i -> i + 1); iterate.forEach(System.out::println); //1,2,3,4,5
Optional增强
-
stream()
如果为空返回一个空流,如果不为空将Optional的值转成一个流。
//返回Optional值的流 Stream<String> stream = Optional.of("Java 11").stream(); stream.forEach(System.out::println); // Java 11 //返回空流 Stream<Object> stream = Optional.ofNullable(null).stream(); stream.forEach(System.out::println); //
-
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
个人感觉这个方法就是结合isPresent()
对Else的增强,ifPresentOrElse
方法的用途是
,如果一个 Optional 包含值,则对其包含的值调用函数 action,
即action.accept(value)
,这与 ifPresent 一致;
与 ifPresent 方法的区别在于,ifPresentOrElse
还有第二个参数emptyAction
——
如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,
即emptyAction.run()
。
Optional<Integer> optional = Optional.of(1); optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() -> System.out.println("Not Present.")); //Value: 1 optional = Optional.empty(); optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() -> System.out.println("Not Present.")); //Not Present.
-
or(Supplier<? extends Optional<? extends T>> supplier)
Optional<String> optional1 = Optional.of("Java"); Supplier<Optional<String>> supplierString = () -> Optional.of("Not Present"); optional1 = optional1.or( supplierString); optional1.ifPresent( x -> System.out.println("Value: " + x)); //Value: Java optional1 = Optional.empty(); optional1 = optional1.or( supplierString); optional1.ifPresent( x -> System.out.println("Value: " + x)); //Value: Not Present
InputStream增强
String lxs = "java"; try ( var inputStream = new ByteArrayInputStream(lxs.getBytes()); var outputStream = new ByteArrayOutputStream()) // { inputStream.transferTo(outputStream); System.out.println(outputStream); //java }
改进的 CompletableFuture API
单位在Timeout类型定义在:java.util.concurrent.Timeunits
中,比如MILLISECONDS
支持 delays 和 timeouts,提升了对子类化的支持。新的工厂方法:
在timeout前以给定的 value 完成这个 CompletableFutrue。返回这个 CompletableFutrue:
public CompletableFuture<T> completeOnTimeout( T value, long timeout, TimeUnit unit)
如果没有在给定的 timeout 内完成,就以java.util.concurrent.TimeoutException
完成这个 CompletableFutrue,并返回这个 CompletableFutrue:
public CompletableFuture<T> orTimeout(long timeout, TimeUnit unit)
使得CompletableFuture可以被更简单的继承。
public <U> CompletableFuture<U> newIncompleteFuture()
返回一个新的以指定 value 完成的CompletionStage, 并且只支持 CompletionStage 里的接口:
<U> CompletionStage<U> completedStage(U value)
返回一个新的以指定异常完成的CompletionStage, 并且只支持 CompletionStage 里的接口:
<U> CompletionStage<U> failedStage(Throwable ex)
改进try-with-resources的异常处理
try-with-resources声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,可以在 try-with-resources 语句中使用该变量, 而无需在 try-with-resources 语句中声明一个新变量。
示例如下:
public static void main(String[] args) throws IOException { System.out.println(readData("test"));// 结果:test } static String readData(String message) throws IOException { Reader inputString = new StringReader(message); BufferedReader br = new BufferedReader(inputString); // Java8处理方式: // try (BufferedReader br1 = br) { // return br1.readLine(); // } // Java9处理方式: try (br) { return br.readLine(); } }
改进 @Deprecated 注解
Java 9 中注解增加了两个新元素:since 和 forRemoval。
- since: 元素指定已注解的API元素已被弃用的版本。
- forRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API。
示例如下:
@Deprecated(since = "1.9", forRemoval = true) class Test{ }
内部类泛型
在java8中,匿名内部类后面的<>
里面必须带有泛型类型。Java9就不需要了:
abstract class Handler<T> { public T content; public Handler(T content) { this.content = content; } abstract void handle(); } public class Test { public static void main(String[] args) { Handler<Integer> intHandler = new Handler<>(1) { @Override public void handle() { System.out.println(content); } }; intHandler.handle(); Handler<? extends Number> intHandler1 = new Handler<>(2) { @Override public void handle() { System.out.println(content); } }; intHandler1.handle(); Handler<?> handler = new Handler<>("test") { @Override public void handle() { System.out.println(content); } }; handler.handle(); } }
Unicode 7.0
从Java SE 9,升级现有平台的API,支持7.0版本的Unicode标准,主要在以下类中:
-
java.lang.Character
和java.lang.String` -
java.text
包中的Bidi
,BreakIterator
和Normalizer
模块化(Module)
模块化就是增加了更高级别的聚合,是Package的封装体。Package是一些类路径名字的约定 ,而模块是一个或多个Package组成的封装体。
- java9以前 :package => class/interface。
- java9以后 :module => package => class/interface。
类比的话相当是Maven中项目可以有子项目,子项目之间还有依赖关系。
但是Maven中子项目的依赖是由pom.xml
来声明的,独立于jvm体系之外。
所以Java要在模块下加上一个module-info.java
文件来声明各个模块中的依赖关系
与访问隔离,编译打包后,就成为一个模块的实体。起到类似于pom.xml的作用。
详细:modules
Jshell
交互模式
-
打开终端,键入
jshell
进入jshell环境; -
输入
/help intro
可以查看Jshell的介绍。 -
键入
/exit
就可以退出。
Jshell默认会导入下面的一些包,所以在Jshell环境中这些包的内容都是可以使用的。
import java.lang.*; import java.io.*; import java.math.*; import java.net.*; import java.nio.file.*; import java.util.*; import java.util.concurrent.*; import java.util.function.*; import java.util.prefs.*; import java.util.regex.*; import java.util.stream.*;
在程序中调用
在JDK9中提供了一个新的类JShell.java
,它属于jdk.jshell
模块。
我们可以使用它执行Java代码片段,或创建一个Java方法,而不用创建一个类。
如果这个Java代码片段中有错误,可以通过Snippet.status
状态
(只有两种状态:REJECTED
、VALID
)来检查。
SourceCodeAnalysis
是用来解析代码的,它使用分号、解析方法、或类的声明等。
例子:用来执行代码的工具类:
package ex.jshell.extension; import jdk.jshell.JShell; import jdk.jshell.Snippet; import jdk.jshell.SnippetEvent; import jdk.jshell.SourceCodeAnalysis; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Objects; /** * This class can execute jshell expressions in sequence * We can write java commands like shell script and execute it. * Just write commands in a way that we give in jshell and save it in a file and execute it. * * @author Hemamabara Vamsi, Kotari * @since 5/27/2017. */ public class JShellScriptExecutor { public static void main(String[] args){ new JShellScriptExecutor().evaluate(args[0]); } public void evaluate(String scriptFileName){ try(JShell jshell = JShell.create()){ // Handle snippet events. We can print value or take action if evaluation failed. jshell.onSnippetEvent(snippetEvent -> snippetEventHandler(snippetEvent)); String scriptContent = new String(Files.readAllBytes(Paths.get(scriptFileName))); String s = scriptContent; while (true) { // Read source line by line till semicolon (;) SourceCodeAnalysis.CompletionInfo an = jshell.sourceCodeAnalysis().analyzeCompletion(s); if (!an.completeness().isComplete()) { break; } // If there are any method declaration or class declaration // in new lines, resolve it. jshell.eval(trimNewlines(an.source())); // EOF if (an.remaining().isEmpty()) { break; } // If there is semicolon, execute next seq s = an.remaining(); } } catch (IOException e) { e.printStackTrace(); } } public void snippetEventHandler(SnippetEvent snippetEvent){ String value = snippetEvent.value(); if(!Objects.isNull(value) && value.trim().length() > 0) { // Prints output of code evaluation System.out.println(value); } // If there are any erros print and exit if(Snippet.Status.REJECTED.equals(snippetEvent.status())){ System.out.println("Evaluation failed : "+snippetEvent.snippet().toString() +"\nIgnoring execution of above script"); } } private String trimNewlines(String s) { int b = 0; while (b < s.length() && s.charAt(b) == '\n') { ++b; } int e = s.length() -1; while (e >= 0 && s.charAt(e) == '\n') { --e; } return s.substring(b, e + 1); } }
关联模块:
module ex.jshell.extension { requires jdk.jshell; }
测试脚本:
String var1 = "Hello"; System.out.println(var1); public int getInt1 () { return 2; } public int getInt2 () { return 4; } getInt1() + getInt2(); public class MyClass{ public void sayHelloWorld() { System.out.println("HelloWorld"); } } new MyClass().sayHelloWorld()
调用:
java ex.jshell.extension.JShellScriptExecutor ./java_shell_code.txt
Java 10
var关键字
var是Java10中新增的局部类型变量推断。它会根据后面的值来推断变量的类型, 所以var必须要初始化。
例:
var a; //❌ var a = 1; //✅
var定义局部变量
var a = 1; // 等于 int a = 1;
var接收方法返回时
var result = this.getResult(); // 等于 String result = this.getResult();
var循环中定义局部变量
for (var i = 0; i < 5; i++) { System.out.println(i); } 等于 for (int i = 0; i < 5; i++) { System.out.println(i); }
var结合泛型
var list1 = new ArrayList<String>(); //在<>中指定了list类型为String // 等于 List<String> list1 = new ArrayList<>(); var list2 = new ArrayList<>(); //<>里默认会是Object
var在Lambda中使用(java11才可以使用)
Consumer<String> Consumer = (var i) -> System.out.println(i); 等于 Consumer<String> Consumer = (String i) -> System.out.println(i);
var不能再哪里使用?
- 类成员变量类型。
- 方法返回值类型。
- Java10中Lambda不能使用var,Java11中可以使用。
完全支持Linux容器(包括docker)
许多运行在Java虚拟机中的应用程序(包括Apache Spark和Kafka等数据服务以及传统的 企业应用程序)都可以在Docker容器中运行。 但是在Docker容器中运行Java应用程序一直存在一个问题, 那就是在容器中运行JVM程序在设置内存大小和CPU使用率后,会导致应用程序的性能下降。 这是因为Java应用程序没有意识到它正在容器中运行。随着Java 10的发布, 这个问题总算得以解决,JVM现在可以识别由容器控制组(cgroups)设置的约束。 可以在容器中使用内存和CPU约束来直接管理Java应用程序,其中包括:
- 遵守容器中设置的内存限制
- 在容器中设置可用的CPU
- 在容器中设置CPU约束
Java 10的这个改进在Docker for Mac、Docker for Windows以及 Docker Enterprise Edition等环境均有效。
Unicode 8.0
增强了java.util.Locale
和相关的API,以实现BCP 47语言标签的其他Unicode扩展。
此次针对BCP 47语言标签扩展包括:
-
cu
(货币类型) -
fw
(一周的第一天) -
rg
(区域覆盖) -
tz
(时区)
具体API变更有:
-
java.text.DateFormat::get*Instance
将根据扩展名返回实例ca
,rg
和/或tz
-
java.text.DateFormatSymbols::getInstance
将根据扩展名返回实例rg
-
java.text.DecimalFormatSymbols::getInstance
将根据扩展名返回实例rg
-
java.text.NumberFormat::get*Instance
将根据扩展名nu
和/或返回实例rg
-
java.time.format.DateTimeFormatter::localizedBy
将返回DateTimeFormatter
基于扩展情况下ca
,rg
和/或tz
-
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
将根据rg
扩展名返回模式字符串。 -
java.time.format.DecimalStyle::of
将DecimalStyle根据扩展名返回实例nu
, 和/
或rg
-
java.time.temporal.WeekFields::of
将WeekFields根据扩展名fw和/或返回实例rg
-
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
将根据扩展名fw
和/
或返回值rg
-
java.util.Currency::getInstance
将Currency根据扩展名cu和/或返回实例rg
-
java.util.Locale::getDisplayName
将返回一个字符串,其中包括这些U扩展名的显示名称 -
java.util.spi.LocaleNameProvider
这些U扩展的键和类型将具有新的SPI
Java 11
移除的包
- com.sun.awt.AWTUtilities。
- sun.misc.Unsafe.defineClass 使用java.lang.invoke.MethodHandles.Lookup.defineClass来替代。
- Thread.destroy() 以及 Thread.stop(Throwable) 方法。
- sun.nio.ch.disableSystemWideOverlappingFileLockCheck 属性。
- sun.locale.formatasdefault 属性。
- jdk snmp 模块。
- javafx,openjdk 是从java10版本就移除了,oracle java10还尚未移除javafx ,而java11版本将javafx也移除了。
- Java Mission Control,从JDK中移除之后,需要自己单独下载。
- Root Certificates :Baltimore Cybertrust Code Signing CA,SECOM ,AOL and Swisscom。
- 在java11中将java9标记废弃的Java EE及CORBA模块移除掉。
Maven插件升级
构建插件的升级主要是maven compile插件的升级,需要升级到3.8.0版本, pandora-boot的maven插件升级到2.1.11.9,依赖如下:
<dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> </dependency>
同时将编译的目标文件和源文件的编译版本指定下:
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.source>11</maven.compiler.source>
移除的模块
<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
lambda参数局部变量
Java11中的lambda表达式可以为隐式类型,其中类型的形式参数都可以被推断出。
对于隐式类型的lambda表达式的形式参数,允许使用保留的类型名称var
,以便:
(var x, var y) -> x.process(y)
等效于:
(x, y) -> x.process(y) // 这样的对的 (var x, int y) -> x.process(y) // 这样就会报错
字符串增强
// 判断字符串是否为空白 " ".isBlank(); // true // 去除首尾空格 " Hello Java11 ".strip(); // "Hello Java11" // 去除尾部空格 " Hello Java11 ".stripTrailing(); // " Hello Java11" // 去除首部空格 " Hello Java11 ".stripLeading(); // "Hello Java11 " // 复制字符串 "Java11".repeat(3); // "Java11Java11Java11" // 行数统计 "A\nB\nC".lines().count(); // 3
HTTP Client API
详情:http-client
改api支持同步和异步两种方式,下面是两种方式的示例:
var request = HttpRequest.newBuilder() .uri(URI.create("https://www.baidu.com/")) .build(); var client = HttpClient.newHttpClient(); // 同步 HttpResponse<String> response = client.send( request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); // 异步 CompletableFuture<HttpResponse<String>> sendAsync = client.sendAsync( request, HttpResponse.BodyHandlers.ofString()); //这里会阻塞 HttpResponse<String> response1 = sendAsync.get(); System.out.println(response1.body());
当然你也可以自定义请求头,比如携带JWT Token权限信息去请求等:
var requestWithAuth = HttpRequest.newBuilder() .uri( URI.create("http://www.xxxxxx.com/sth") ) .header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9." + "eyJzdWIiOiIxNTIwNTE2MTE5NiIsImNyZWF0ZWQiOjE1ODMzM" + "TA2ODk0MzYsImV4cCI6MTU4MzM5NzA4OSwidXNlcmlkIjoxMD" + "AwNH0.OE9R5PxxsvtVJZn8ne-ksTb2aXXi7ipzuW9kbCiQ0uN" + "oW0fJJr_wckLFmgDzxmBs3IdzIhWDAtaSIvmTshK_RQ").GET().build(); var response = HttpClient.newHttpClient() .send( requestWithAuth, HttpResponse.BodyHandlers.ofString() ); System.out.println( response.body() ); // 打印获取到的接口返回内容
文件读写增强
1、Files类增强
我们以前心心念的直接能把文件内容读取到String以及String回写到文件的功能终于支持了, 可以通过Files类的静态方法writeString()和readString()完成:
Path path = Paths.get("/Users/CodeSheep/test.txt"); String content = Files.readString(path, StandardCharsets.UTF_8); System.out.println(content); Files.writeString( path, "王老七", StandardCharsets.UTF_8 );
2、InputStream增强
InputStream则增加了一个transferTo()方法,直接将数据丢到OutputStream去:
InputStream inputStream = new FileInputStream( "/Users/CodeSheep/test.txt" ); OutputStream outputStream = new FileOutputStream( "/Users/CodeSheep/test2.txt" ); inputStream.transferTo( outputStream );
直接运行java文件
增强java启动器以运行作为Java源代码的单个文件提供的程序, 包括通过shebang文件和相关技术从脚本内部使用该程序。
从JDK 10开始,java启动器以三种模式运行:
- 启动类文件,
- 启动JAR文件的main类
- 启动模块的main类
Java 11中增加了新的第四种模式:启动在源文件中声明的类。
java Java11.java
如果文件没有.java
扩展名,则必须使用选项--source
来强制源文件模式。
例如当源文件是要执行的“脚本”并且源文件的名称不遵循Java源文件的常规命名约定时。
Java 12
switch语句扩展
扩展switch语句,以便可以将其用作语句或表达式, 并且两种形式都可以使用“传统”或“简化”作用域并控制流的行为。 这些变化将简化日常编码在switch中。这是JDK 12中的预览功能。
请注意:此JEP已被JDK 13的JEP 354取代。
普通写法:
switch (day) { case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; }
现在引入一种新的switch标签形式,写为case L ->
,表示如果匹配标签,
则只执行标签右边的代码。例如,现在可以编写以前的代码:
switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); }
再比如局部变量,普通写法是这样的:
int numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = 8; break; case WEDNESDAY: numLetters = 9; break; default: throw new IllegalStateException("Wat: " + day); }
现在的写法是这样的:
int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; };
API变化与废弃
xml
Jaxb被移除
Jaxb被移除,要导入:
<!-- ... --> </properties> <!-- ... --> <junit.version>4.12</junit.version> <jaxb-core.version>2.3.0</jaxb-core.version> <jaxb-api.version>2.3.0</jaxb-api.version> <jaxb-impl.version>2.3.0</jaxb-impl.version> <!-- ... --> </properties> <!-- ... --> <dependencyManagement> <!-- ... --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>${jaxb-api.version}</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>${jaxb-core.version}</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>${jaxb-impl.version}</version> </dependency> <!-- ... --> </dependencyManagement>
网络相关
ssl
import java.security.cert.CertificateException; X509TrustManager x509tm = new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } }; newBuilder().sslSocketFactory(createSSLSocketFactory(), x509tm)
Java 13
文本直排
String html = """ <html> <body> <p>Hello, world</p> </body> </html> """;
switch表达式预览版
JDK 13中新增 switch 表达式beta 版本,这是对Java12 switch表达式功能的增强版本, 并且Java13版本的switch表达式的更新可以用于生产环境中。 switch 表达式扩展了 switch 语句,使其不仅可以作为语句(statement), 还可以作为表达式(expression),并且两种写法都可以使用传统的 switch 语法。
除了Java12的用法之外,Java13的更新引入一个新的关键字yield。大多数switch表达式在
case L ->
开关标签的右侧都有一个表达式。如果需要一个完整的块,
需要使用yield语句来产生一个值,该值是封闭switch表达式的值。示例:
int j = switch (day) { case MONDAY -> 0; case TUESDAY -> 1; default -> { int k = day.toString().length(); int result = f(k); yield result; } };
上例也可以使用传统的switch语句:
int result = switch (s) { case "Foo": yield 1; case "Bar": yield 2; default: System.out.println("Neither Foo nor Bar, hmmm..."); yield 0; };
switch表达的情况必须详细;对于所有可能的值,必须有一个匹配的switch标签。 (显然,switch声明并非必须详细。)这通常意味着需要一个default子句。 但是enum switch对于覆盖所有已知常量的表达式,default编译器会插入一个子句以指示该 enum定义在编译时和运行时之间已更改。
依靠这种隐式default子句的插入可以使代码更健壮。现在,当重新编译代码时, 编译器将检查所有情况是否得到明确处理。
此外,switch表达式必须以一个值正常完成,或者必须通过引发异常来突然完成。 这有许多后果。首先,编译器会检查每个开关标签是否匹配,然后产生一个值。
示例:
int i = switch (day) { case MONDAY -> { System.out.println("Monday"); // ERROR! Block doesn't contain a yield statement } default -> 1; }; i = switch (day) { case MONDAY, TUESDAY, WEDNESDAY: yield 0; default: System.out.println("Second half of the week"); // ERROR! Group doesn't contain a yield statement };
另一种后果是,控制语句,break,yield,return和continue,无法通过跳switch表达式,示例:
z: for (int i = 0; i < MAX_VALUE; ++i) { int k = switch (e) { case 0: yield 1; case 1: yield 2; default: continue z; // ERROR! Illegal jump through a switch expression }; // ... }
Java 15
https://my.oschina.net/waylau/blog/4633203
Java 17
在Spring 6.x和Spring Boot 3.x中,已经强制要求最低版本为Java 17。
ForkJoinPool 的bug
ForkJoinPool在JDK17上挂起的bug
JDK17竟然有个大Bug:ForkJoinPool在单核机器上会挂起,平时开发都是4核8核,结果扔到AWS的单核跑就挂了:
https://bugs.openjdk.java.net/browse/JDK-8274349临时解决方案:加启动参数:
-Djava.util.concurrent.ForkJoinPool.common.parallelism=1
或者在main()
的第一行开始写:
if (Runtime.getRuntime().availableProcessors() <= 1) { System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "1"); }
恢复严格的浮点语义
在以前的Java版本中,为了提高性能,有时候会对浮点运算进行优化, 这就导致结果可能不符合严格的浮点运算规则。 在Java 17中,恢复了浮点运算的严格要求,确保浮点运算的结果更符合预期, 避免了一些潜在的精度问题。这一特性是底层的改变,而不是代码层面的改变, 因此,若想让浮点运算更为严格,只需将JDK升级为17即可。
提供更强大的伪随机数生成器
Java 17引入了一些增强的伪随机数生成器(PRNG)算法,以提高随机数生成的质量和性能。
public class Solution { public static void main(String[] args) { String[] algorithms = { "L32X64MixRandom", // "L32X64StarStarRandom", // "L64X128MixRandom", "L64X128StarStarRandom", "L64X256MixRandom", "L64X1024MixRandom", "L128X128MixRandom", "L128X256MixRandom", "L128X1024MixRandom", "Xoshiro256PlusPlus", "Xoroshiro128PlusPlus" }; for(String algorithm: algorithms){ try { RandomGenerator randomGenerator = RandomGenerator.of(algorithm); int randomNum = randomGenerator.nextInt(); System.out.println(algorithm+"=>" + randomNum); }catch (IllegalArgumentException e){ System.out.println(algorithm); } } } }
删除Applet API
Java Applet是一种能运行在网页上的小程序,在JDK 9的时候,Applet API就被标记为过时,直到JDK 17才将Applet API进行了删除。
增强switch的自动匹配能力(预览)
在之前的Java版本中,switch语句只能基于常量值进行匹配,而引入了模式匹配后,switch语句可以使用更灵活的模式来匹配表达式的值。
public class Solution { public static void main(String[] args) { Object obj = "Hello"; switch (obj) { case String s -> System.out.println("String length: " + s.length()); case Integer i -> System.out.println("Integer value: " + i); default -> System.out.println("Unknown type"); } } }
注意:switch的模式匹配在JDK 17中只处在预览中,要想使用模式匹配,
需要在编译时添加--enable-preview
参数开启。例如:
javac --enable-preview --release 17 Solution.java java --enable-preview Solution
封闭类(Sealed Class)
封闭类允许开发者限制一个类的子类的继承关系,从而控制类的层次结构,
提高代码的安全性和可维护性。在Java 17中,
我们可以使用sealed
关键字来声明一个封闭类,
同时使用permits
关键字列出该类允许继承的子类。
封闭类的子类可以继续使用sealed
来限制子类,
或者使用no-sealed
关键字来表示子类不受限制。
// 定义一个受限制的Shape封闭类,该类只允许被Circle和Rectangle继承。 sealed class Shape permits Circle, Rectangle { } // 封闭类的子类只允许用final、sealed、non-sealed修饰 final class Circle extends Shape { } non-sealed class Rectangle extends Shape { } class OtherShape extends Rectangle { }
废弃安全管理器API
安全管理器是Java平台中用于控制应用程序对系统资源的访问权限的重要组件。 然而随着Java平台的发展和安全模型的变化,安全管理器的使用逐渐减少。 JDK 17或许向开发者发出了信号,鼓励开发者不再依赖于安全管理器, 而是寻找其它替代方案来管理程序的安全性。
Java 21 新特性
虚拟线程
虚拟线程在JDK 19中就被提出, 并作为预览部分,直到JDK 21才正式作为Java新特性。 线程和进程一样,都是程序中非常昂贵的资源, 因此我们的Java程序一般都会限制线程大小。比如常用的Tomcat, 每一个请求都对应一个线程处理, 同时限制处理请求的线程数量防止线程过多而导致服务器崩溃。 这在一定程度上限制了Web服务的吞吐量。对于这种需要提高吞吐量的场景, 使用虚拟线程将会大大改善这种情况。
对开发者来说,虚拟线程在使用体验上和Thread几乎没有区别,相比之下, 虚拟线程资源占用得非常少,同时,虚拟线程是一种即用即启动的资源, 不应该被池化存储。下面将通过一个小实验来对比虚拟线程和传统线程的处理能力。
创建10000个线程,每个线程被创建出来后休眠1s再结束,计算整个过程用时。
import java.time.Duration; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class Solution { public static void main(String[] args) { long start = System.currentTimeMillis(); try (var executor = Executors.newFixedThreadPool(10000)){ IntStream.range(0,10_000).forEach(i -> { executor.submit(()->{ Thread.sleep(Duration.ofSeconds(1)); return i; }); }); } long end = System.currentTimeMillis(); System.out.println("use time:" + (end - start) + "ms"); } }
传统操作会在突破线程限制时报错:java.lang.OutOfMemoryError
import java.time.Duration; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class Solution { public static void main(String[] args) { long start = System.currentTimeMillis(); try(var executor = Executors.newVirtualThreadPerTaskExecutor()){ IntStream.range(0,10_000).forEach(i -> { executor.submit(()->{ Thread.sleep(Duration.ofSeconds(1)); return i; }); }); } long end = System.currentTimeMillis(); System.out.println("use time:" + (end - start) + "ms"); } } // 用时:1135ms
统一有序集合的操作接口
Java中的集合类(List
、Deque
、Linked
种类的set
和map
)
一般使用频率非常高,
但是在JDK21之前,这些集合类的使用操作并不统一,容易造成混乱。
在JDK 21中,增加了SequencedCollection
、SequencedSet
和SequencedMap
接口,
并且在SequencedColletion
接口中定义了有序集合中常用的操作方法:
- addFirst
- addLast
- getFirst
- getLast
- removeFirst
- removeLast
- reversed
在SequencedMap
接口中又增加了Map的操作方法:
- firstEntry
- lastEntry
- pollFirstEntry
- pollLastEntry
- putFirst
- putLast
- reversed
- sequencedEntrySet
- sequencedKeySet
- sequencedValues
switch模式匹配
在JDK 17中,switch的模式匹配被划为预览中,需要通过--enable-priview参数才能开启。 但在JDK 21中,已经正式支持了。
record模式
record模式用于定义数据对象的结构,该数据对象不允许继续修改。 record模式是Java面向数据编程的基础,并将在以后逐渐发展, 简单的演示:
record Person(String name,Integer age){}; public class Solution { public static void main(String[] args) { Person person = new Person("Bob",21); if(person instanceof Person(String name,Integer age)){ System.out.println(name + " " + age); } } }