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的作用。

JDK中自己的模块

那么JDK被拆为了哪些模块呢?打开终端执行java --list-modules查看。

$ java --list-modules
java.base@11.0.2
java.compiler@11.0.2
java.datatransfer@11.0.2
java.desktop@11.0.2
java.instrument@11.0.2
java.logging@11.0.2
java.management@11.0.2
java.management.rmi@11.0.2
java.naming@11.0.2
java.net.http@11.0.2
java.prefs@11.0.2
java.rmi@11.0.2
java.scripting@11.0.2
java.se@11.0.2
java.security.jgss@11.0.2
java.security.sasl@11.0.2
java.smartcardio@11.0.2
java.sql@11.0.2
java.sql.rowset@11.0.2
java.transaction.xa@11.0.2
java.xml@11.0.2
java.xml.crypto@11.0.2
jdk.accessibility@11.0.2
jdk.aot@11.0.2
jdk.attach@11.0.2
jdk.charsets@11.0.2
jdk.compiler@11.0.2
jdk.crypto.cryptoki@11.0.2
jdk.crypto.ec@11.0.2
jdk.dynalink@11.0.2
jdk.editpad@11.0.2
jdk.hotspot.agent@11.0.2
jdk.httpserver@11.0.2
jdk.internal.ed@11.0.2
jdk.internal.jvmstat@11.0.2
jdk.internal.le@11.0.2
jdk.internal.opt@11.0.2
jdk.internal.vm.ci@11.0.2
jdk.internal.vm.compiler@11.0.2
jdk.internal.vm.compiler.management@11.0.2
jdk.jartool@11.0.2
jdk.javadoc@11.0.2
jdk.jcmd@11.0.2
jdk.jconsole@11.0.2
jdk.jdeps@11.0.2
jdk.jdi@11.0.2
jdk.jdwp.agent@11.0.2
jdk.jfr@11.0.2
jdk.jlink@11.0.2
jdk.jshell@11.0.2
jdk.jsobject@11.0.2
jdk.jstatd@11.0.2
jdk.localedata@11.0.2
jdk.management@11.0.2
jdk.management.agent@11.0.2
jdk.management.jfr@11.0.2
jdk.naming.dns@11.0.2
jdk.naming.rmi@11.0.2
jdk.net@11.0.2
jdk.pack@11.0.2
jdk.rmic@11.0.2
jdk.scripting.nashorn@11.0.2
jdk.scripting.nashorn.shell@11.0.2
jdk.sctp@11.0.2
jdk.security.auth@11.0.2
jdk.security.jgss@11.0.2
jdk.unsupported@11.0.2
jdk.unsupported.desktop@11.0.2
jdk.xml.dom@11.0.2
jdk.zipfs@11.0.2

大家都知道JRE中有一个超级大的rt.jar(60多M),tools.jar也有几十兆, 以前运行一个hello world也需要上百兆的环境。

让Java SE程序更加容易轻量级部署。强大的封装能力。改进组件间的依赖管理, 引入比jar粒度更大的Module。改进性能和安全性。

例:分模块

其实只要在原来Maven的子项目的源代码根目录加上一个module-info.java, 指定哪些类是对外的,要依赖哪些类就可以:

example-module/
|-> pom.xml
|
|-> example-common/
|   |-> src/main/java/
|   |   |-> study/module/common/base/
|   |   |   `-> SimpleRenderer.java
|   |   |-> study/module/common/helper/
|   |   |   `-> RendererSupport.java
|   |   `-> module-info.java
|   `-> pom.xml
|
`-> example-ui/
    |-> src/main/java/
    |   |-> study/module/ui/item/
    |   |   `-> Component.java
    |   `-> module-info.java
    `-> pom.xml

总项目pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>study.example</groupId>
	<artifactId>study-module</artifactId>
	<version>1.0.0</version>
	<packaging>pom</packaging>

	<modules>
		<module>example-common</module>
		<module>example-ui</module>
	</modules>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.version>1.0.0</project.version>
	</properties>

	<dependencyManagement>
		<dependencies>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>11</source>
					<target>11</target>
					<encoding>${project.build.sourceEncoding}</encoding>
				</configuration>
				<version>3.8.1</version>
			</plugin>
		</plugins>
	</build>

</project>

子项目两个包:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>study.example</groupId>
		<artifactId>study-module</artifactId>
		<version>1.0.0</version>
	</parent>

	<artifactId>example-common</artifactId>

	<packaging>jar</packaging>

</project>

工具类模块两个包,一个对外一个对内:

对内:

package study.module.common.base;

public class SimpleRenderer {

	public void renderAsString(Object object) {
		System.out.println(object);
	}

}

对外:

package study.module.common.helper;

import study.module.common.base.SimpleRenderer;

public class RendererSupport {

	public void render(Object object) {
		new SimpleRenderer().renderAsString(object);
	}

}

指定这个模块对外的包:

module example.common {
	requires java.base;
	
	exports study.module.common.helper;
}

界面模块会引用工具类的模块:

POM文件指定MAVEN项目的引用:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>study.example</groupId>
		<artifactId>study-module</artifactId>
		<version>1.0.0</version>
	</parent>

	<artifactId>example-ui</artifactId>
	<packaging>jar</packaging>

	<dependencies>
		<dependency>
			<groupId>study.example</groupId>
			<artifactId>example-common</artifactId>
			<version>${project.version}</version>
		</dependency>
	</dependencies>

</project>

模块的声明文件中指定对工具模块的引用:

module example.ui {
	requires java.base;
	
	requires example.common;
}

具体的类中使用工具模块中的类:

package study.module.ui.item;

import study.module.common.helper.RendererSupport;

public class Component {

	public static void main(String[] args) {
		RendererSupport support = new RendererSupport();
		support.render("test object");
	}

}

查看jar包依赖哪些模块:

$ jdeps --list-deps  example-common/target/example-common-1.0.0.jar
   java.base

模块的关键字

open

用来指定开放模块,开放模块的所有包都是公开的,public的可以直接引用使用, 其他类型可以通过反射得到。

open module module.one {
 //导入日志包
 requires java.logging;
}

opens

opens 用来指定开放的包,其中public类型是可以直接访问的,其他类型可以通过反射得到。

module module.one {
 opens <package>;
}

exports

exports用于指定模块下的哪些包可以被其他模块访问。

module module.one {
 
 exports <package>;
 
 exports <package> to <module1>, <module2>...;
}

requires

该关键字声明当前模块与另一个模块的依赖关系。

module module.one {
 requires <package>;
}

uses、provides … with …

uses语句使用服务接口的名字,当前模块就会发现它,使用java.util.ServiceLoader 类进行加载,必须是本模块中的,不能是其他模块中的.其实现类可以由其他模块提供。

module module.one {
 //对外提供的接口服务 ,下面指定的接口以及提供服务的impl,如果有多个实现类,用用逗号隔开
 uses <接口名>;
 provides <接口名> with <接口实现类>,<接口实现类>;
}

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

改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

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");
}