Jade Dungeon

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

注意:

  • 使用ofcopyOf创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作, 不然会报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包中的BidiBreakIteratorNormalizer

模块化(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状态 (只有两种状态:REJECTEDVALID)来检查。

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将根据扩展名返回实例carg和/或tz
  • java.text.DateFormatSymbols::getInstance将根据扩展名返回实例 rg
  • java.text.DecimalFormatSymbols::getInstance将根据扩展名返回实例 rg
  • java.text.NumberFormat::get*Instance将根据扩展名nu和/或返回实例rg
  • java.time.format.DateTimeFormatter::localizedBy将返回DateTimeFormatter 基于扩展情况下carg和/或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中的集合类(ListDequeLinked种类的setmap) 一般使用频率非常高, 但是在JDK21之前,这些集合类的使用操作并不统一,容易造成混乱。

在JDK 21中,增加了SequencedCollectionSequencedSetSequencedMap接口,

并且在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);
		}
	}

}