Java 8
主要特性:
- Lambda, 函数式接口,方法引用,Optional, Stream的引入
- 接口默认方法,重复注解,日期API, Base64支持
Lambda和函数式接口
函数式接口就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。可以隐式转化为 Lambda 表达式。
@FunctionalInterface
interface Operation {
int operation(int a, int b);
}
class Test {
private int operate(int a, int b, Operation operation) {
return operation.operation(a, b);
}
}
Test test = new Test();
test.operate(1, 2, (a, b) -> a + b);
方法引用
通过方法引用,可以使用方法的名字来指向一个方法。使用一对冒号来引 "::" 用方法。
构造方法引用
使用Class:new
Test test = Test.create(Test::new);
静态方法引用
使用方式:Class::staticMethod
test.operate(1, 2, Test::add);
实例方法引用
使用方式:instance::method
test.operate(1, 2, test::sub);
接口默认方法
Java 8 新增了接口的默认实现,通过 default 关键字表示。
public interface TestInterface {
// 接口默认方法
default String test() {
return "default";
}
}
重复注解
Java 8 支持了重复注解。在 Java 8 之前想实现重复注解,需要用一些方法来绕过限制。比如下面的代码。
@interface Author {
String name();
}
@interface Authors {
Author[] value();
}
@Authors({@Author(name="a"), @Author(name = "b")})
class Article {
}
而在 Java 8 中,可以直接用下面的方式。
@Repeatable(Authors.class)
@interface Author {
String name();
}
@interface Authors {
Author[] value();
}
@Author(name = "a")
@Author(name = "b")
class Article {
}
在解析注解的时候,Java 8 也提供了新的 API。
AnnotatedElement.getAnnotationsByType(Class<T>)
类型注解
Java 8 之前注解只能用在声明中,在 Java 8 中,注解可以使用在 任何地方。
@Author(name="a")
private Object name = "";
private String author = (@Author(name="a")String) name;
更好的类型推断
Java 8 对于类型推断做了改进。
比如在 Java 7 中下面的写法:
List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.<String>asList());
在 Java 8 中改进后的写法,可以自动做类型推断。
List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.asList());
Optional
Java 8 中新增了 Optional 类用来解决空指针异常。Optional 是一个可以保存 null 的容器对象。通过 isPresent() 方法检测值是否存在,通过 get() 方法返回对象。
// 创建一个 String 类型的容器
Optional<String> str = Optional.of("str");
// 值是否存在
boolean pre = str.isPresent();
// 值如果存在就调用 println 方法,这里传入的是 println 的方法引用
str.ifPresent(System.out::println);
// 获取值
String res = str.get();
// 传入空值
str = Optional.ofNullable(null);
// 如果值存在,返回值,否则返回传入的参数
res = str.orElse("aa");
str = Optional.of("str");
// 如果有值,对其调用映射函数得到返回值,对返回值进行 Optional 包装并返回
res = str.map(s -> "aa" + s).get();
// 返回一个带有映射函数的 Optional 对象
res = str.flatMap(s -> Optional.of(s + "bb")).flatMap(s -> Optional.of(s + "cc")).get();
Stream
Java 8 中新增的 Stream 类提供了一种新的数据处理方式。这种方式将元素集合看做一种流,在管道中传输,经过一系列处理节点,最终输出结果。
List<String> list = Arrays.asList("maa", "a", "ab", "c");
list.stream()
.filter(s -> s.contains("a"))
.map(s -> s + "aa")
.sorted()
.forEach(System.out::println);
System.out.println("####");
list.parallelStream().forEach(System.out::println);
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
int res = numbers.stream().map(i -> i + 1).mapToInt(i -> i).summaryStatistics().getMax();
System.out.println(res);
日期时间API
Java 8 中新增了日期时间 API 用来加强对日期时间的处理,其中包括了 LocalDate,LocalTime,LocalDateTime,ZonedDateTime 等等,关于 API 可以参照官方文档以及这篇博客,写的很详细。下面是示例代码。
LocalDate now = LocalDate.now();
System.out.println(now);
System.out.println(now.getYear());
System.out.println(now.getMonth());
System.out.println(now.getDayOfMonth());
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalDateTime localDateTime = now.atTime(localTime);
System.out.println(localDateTime);
Base64支持
Java 8 标准库中提供了对 Base 64 编码的支持。具体 API 见可参照文档。下面是示例代码。
String base64 = Base64.getEncoder().encodeToString("aaa".getBytes());
System.out.println(base64);
byte[] bytes = Base64.getDecoder().decode(base64);
System.out.println(new String(bytes));
并行数组ParallelSort
Java 8 中提供了对数组的并行操作,包括 parallelSort 等等,具体可参照 API。
Arrays.parallelSort(new int[] {1, 2, 3, 4, 5});
Java 11
主要特性:
- 接口私有方法、集合不可变工厂方法、局部类型推断
- 改进try-with-resource, String底层数据结构变更,字符串增强,钻石操作符增强,Stream, Optional增强
- G1称为默认收集器,G1并行full gc,引入ZGC, Epsilon, Graal垃圾收集器
接口中使用私有方法
Java 9 中可以在接口中定义私有方法。示例代码如下:
public interface TestInterface {
String test();
// 接口默认方法
default String defaultTest() {
pmethod();
return "default";
}
private String pmethod() {
System.out.println("private method in interface");
return "private";
}
}
集合不可变实例工厂方法
在以前,我们想要创建一个不可变的集合,需要先创建一个可变集合,然后使用 unmodifiableSet 创建不可变集合。代码如下:
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");
set = Collections.unmodifiableSet(set);
System.out.println(set);
Java 9 中提供了新的 API 用来创建不可变集合。
List<String> list = List.of("A", "B", "C");
Set<String> set = Set.of("A", "B", "C");
Map<String, String> map = Map.of("KA", "VA", "KB", "VB");
新增局部类型推断 var
var关键字用于局部变量, for循环,lambda表达式中
局部变量及for循环(10)
var a = "aa";
System.out.println(a);
lambda(11)
(var x, var y) -> x.process(y)
改进 try-with-resources
Java 9 中不需要在 try 中额外定义一个变量。Java 9 之前需要这样使用 try-with-resources:
InputStream inputStream = new StringBufferInputStream("a");
try (InputStream in = inputStream) {
in.read();
} catch (IOException e) {
e.printStackTrace();
}
在 Java 9 中可以直接使用 inputStream 变量,不需要再额外定义新的变量了。
InputStream inputStream = new StringBufferInputStream("a");
try (inputStream) {
inputStream.read();
} catch (IOException e) {
e.printStackTrace();
}
String 底层存储结构发生变化使用字节数组
字符串 API 增强
Java 11 新增了 一系列字符串处理方法,例如:
// 判断字符串是否为空白
" ".isBlank();
" Javastack ".stripTrailing(); // " Javastack"
" Javastack ".stripLeading(); // "Javastack "
增强了钻石操作符 "<>",可以在 匿名内部类中使用了。
在 Java 9 之前,内部匿名类需要指定泛型类型,如下:
Handler<? extends Number> intHandler1 = new Handler<Number>(2) {
}
而在 Java 9 中,可以自动做类型推导,如下:
Handler<? extends Number> intHandler1 = new Handler<>(2) {
}
增强了 Stream,Optional,Process API
takeWhile 从Stream中依次获取满足条件的元素,直匹配到一个不满足条件为止结束获取,不同与filter
List<Integer> list = List.of(11,33,44,102,232,454,67,556,46,78);
list.stream().takeWhile(x -> x < 100).forEach(System.out::println);
// 输出结果如下
11
33
44
dropWhile 从Stream中依次删除满足条件的元素,直到匹配到一个不满足条件为止结束删除
List<Integer> list = List.of(11,33,44,102,232,454,67,556,46,78);
list.stream().dropWhile(x -> x < 100).forEach(System.out::println);
// 输出结果如下
102
232
454
67
556
46
78
新增Stream.ofNullable(null) 允许单一的null元素
Stream.iterate重载方法
Stream.iterate(0, k -> k + 1).limit(12).forEach(System.out::println);
// 9 新增的重载方法
Stream.iterate(0, k -> k < 12, k -> k + 1).forEach(System.out::println);
Optional新增元素转化为stream方法等
Optional.of(List.of(1, 23, 4, 5, 6)).stream().forEach(System.out::println);
新增方法orElseThrow和ifPresentOrElse。
首先是orElseThrow,如果没有值,则使用该orElseThrow方法抛出NoSuchElementException。否则,它返回一个值。
public Person getPersonById(Long id) {
Optional<Person> personOpt = repository.findById(id);
return personOpt.orElseThrow();
}
第二种有趣的方法是ifPresentOrElse。如果存在一个值,它将使用该值执行给定的操作。否则,它将执行给定的基于空的操作。
public void printPersonById(Long id) {
Optional<Person> personOpt = repository.findById(id);
personOpt.ifPresentOrElse(
System.out::println,
() -> System.out.println("Person not found")
);
}
模块化
什么是模块?
官方对模块的定义为:一个被命名的,代码和数据的自描述集合。( the module, which is a named, self-describing collection of code and data)。
为什么模块化?
显示依赖管理:每个模块需要显式声明自己需暴露的包,而自己所依赖的和自己内部使用的包,则不会暴露,也不会被外部引用到,大大的减少Jar包冲突的情况。
强封装性:模块显示选择向其他模块只暴露需要的类或者接口,而完美隐藏了内部实现细节及其内部成员,实现真正的封装性。
安全性:显式依赖管理及强封装性,大大的减少了程序运行时不必要模块的加载,减少了Java运行期间的被攻击面。
规范性:显示的声明暴露的内容,可以让第三方库的开发者更好的管理自己的内部实现逻辑和内部类。第三方库作者可以更轻松的管理自己的内部类的访问权限和反射调用权限,避免了出现sun.misc.BASE64Encoder这些内部类在已经被官方声明了过时和不建议使用的前提下,仍有大量的开发者去随意使用的情况。
自定义最小运行时映射:Java因为其向后兼容的原则,不会轻易对其内容进行删除,包含的陈旧过时的技术也越来越多,导致JDK变得越来越臃肿。而Java9的显示依赖管理使得加载最小所需模块成为了可能,我们可以选择只加载必须的JDK模块,抛弃如java.awt, javax.swing, java.applet等这些用不到的模块。这种机制,大大的减少了运行Java环境所需要的内存资源,在对于嵌入式系统开发或其他硬件资源受限的场景下的开发非常有用。
制作最小运行时映像:
使用jlink工具来创建最小运行时映像。以下是一个示例命令,其中$JAVA_HOME变量引用了您安装的Java 9 JDK的根目录,并将最小运行时映像创建在myimage目录中:
$ $JAVA_HOME/bin/jlink --module-path $JAVA_HOME/jmods --add-modules $modules --output myimage
请注意,上面的命令假定您的模块列表已保存在名为modules的变量中。如果您没有将模块列表保存到变量中,请在上面的命令中将$modules替换为逗号分隔的模块列表。
运行最小运行时映像。以下是一个示例命令,其中$JAVA_HOME变量引用了您安装的Java 9 JDK的根目录,并且myimage目录包含了您创建的最小运行时映像:
$ myimage/bin/java --list-modules
上面的命令将列出您的最小运行时映像中包含的模块。
怎么模块化?
/x1/java/jdk11/jmods$ ../bin/jmod describe java.logging.jmod
java.logging@11
exports java.util.logging
requires java.base mandated
provides jdk.internal.logger.DefaultLoggerFinder with sun.util.logging.internal.LoggingProviderImpl
contains sun.net.www.protocol.http.logging
contains sun.util.logging.internal
contains sun.util.logging.resources
platform linux-amd64
requires 表示这个模块需要依赖哪个包,base包是基础肯定是需要依赖的
exports 表示这个模块可以提供什么给其他模块使用,这里定义了java.util.logging,就是日志模块的主要对外暴露的功能
provides with 表示这个模块提供了哪个Service能力,我们知道Java开发中经常使用ServiceLoader来加载只使用接口,而实现是通过声明来提供的类。这里DefaultLoggerFinder就是接口,而LoggingProviderImpl就是实现类
contains 表示这个模块中还有哪些类包,但并不对外暴露
platform 表示是linux amd6架构体系的
use 说明应用系统可以使用哪个Service接口
open 表示对外暴露的功能还允许通过反射访问,很多开发框架都需要这个能力
模块export对外暴露可以使用to关键字定向限定只有某一个模块才可以访问这个模块
如果使用transitive关键字,表示可以传递对外暴露能力。
为了保证兼容性,除了正规的module(带有module-info且位于module path下)之外,还有两种特殊的module来为向后兼容或者说辅助迁移提供帮助。
Unnamed Module
每个classloader在classpath下加载的所有JAR(不管是否模块化)共同组成一个unnamed module(未命名模块),未命名模块自动声明依赖所有的显式模块,同时exports自己的所有包,而一个显式模块并不能声明依赖未命名模块。
Automatic Module
模块系统会为在module path上找到的每个JAR包创建一个内部模块,对于模块化的JAR包来说,因为包含了module-info文件,它的模块名、依赖、导出等都是有明确描述的,所以没有什么问题。但是迁移的过程中无法避免非模块化的JAR包依赖。这种情况下模块系统会为它自动创建一个模块,即Automatic module(自动模块),并且对该模块的属性进行最安全的补全。
Name:对于模块的名称,如果在MANIFEST文件中定义了Automatic-module-name这个header则以此值为准,否则使用JAR包文件名。
Requires:模块系统允许自动模块读取所有其他模块,也就是说自动模块依赖其他所有模块。与其他显式定义的正规模块不同,自动模块可以读取未命名模块。
Exports / Opens:模块系统Export、Open Jar包内的所有package
多版本兼容 jar 包
多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。通过 --release 参数指定编译版本。具体的变化就是 META-INF 目录下 MANIFEST.MF 文件新增了一个属性:
Multi-Release: true
然后 META-INF 目录下还新增了一个 versions 目录,如果是要支持 java9,则在 versions 目录下有 9 的目录。
multirelease.jar
├── META-INF
│ └── versions
│ └── 9
│ └── multirelease
│ └── Helper.class
├── multirelease
├── Helper.class
└── Main.class
实例
创建文件夹 c:/test/java7/com/runoob,并在该文件夹下创建 Test.java 文件,代码如下:
package com.runoob;
public class Tester {
public static void main(String[] args) {
System.out.println("Inside java 7");
}
}
创建文件夹 c:/test/java9/com/runoob,并在该文件夹下创建 Test.java 文件,代码如下:
package com.runoob;
public class Tester {
public static void main(String[] args) {
System.out.println("Inside java 9");
}
}
编译源代码
C:\test > javac --release 9 java9/com/runoob/Tester.java
C:\JAVA > javac --release 7 java7/com/runoob/Tester.java
创建多版本兼容 jar 包
C:\JAVA > jar -c -f test.jar -C java7 . --release 9 -C java9 .
Warning: entry META-INF/versions/9/com/runoob/Tester.java,
multiple resources with same name
使用 JDK 7 执行:
C:\JAVA > java -cp test.jar com.runoob.Tester
Inside Java 7
使用 JDK 9 执行:
C:\JAVA > java -cp test.jar com.runoob.Tester
Inside Java 9
G1 成为默认垃圾收集器(Jdk9)
在 Java 8 的时候,默认垃圾回收器是 Parallel Scavenge(新生代)+Parallel Old(老年代)。到了 Java 9, CMS 垃圾回收器被废弃了,G1(Garbage-First Garbage Collector) 成为了默认垃圾回收器。G1 是在 Java 7 中被引入的,经过两个版本优异的表现成为成为默认垃圾回收器。
HTTP Client
在 JDK 11 中 Http Client API 得到了标准化的支持。且支持 HTTP/1.1 和 HTTP/2 ,也支持 websockets。
使用起来也很简单,如下:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
支持TLS 1.3 协议
实现TLS协议1.3版本, 替换了之前版本中包含的 TLS,包括 TLS 1.2,同时还改进了其他 TLS 功能, 在安全性和性能方面也做了很多提升
多线程并行 GC
在JDK9中G1被选定为默认的垃圾收集器,G1的设计目标是避免发生Full GC,由于Full GC较难产生所以在设计之初只有Young GC和Mixed GC是并行的,而Full GC是单线程使用标记-清理-合并算法进行垃圾回收。G1只是避免发生Full GC,在极端情况下,当G1的回收速度相对于产生垃圾的速度不是足够快时,就会发生Full GC。
为了最大限度地减少 Full GC 造成的应用停顿的影响,从 JDK 10开始,G1 的 FullGC 改为并行的标记清除算法,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量
线程的数量可以由 -XX:ParallelGCThreads 选项来控制,这个参数也用来控制Young GC和Mixed GC的线程数。
引入了实验性的ZGC以及Epsilon垃圾回收器
基于Java的实验性JIT编译器Graal
Graal 是一个以 Java 为主要编程语言,面向 Java bytecode 的编译器。与用 C++ 实现的 C1 及 C2 相比,它的模块化更加明显,也更加容易维护。Graal 既可以作为动态编译器,在运行时编译热点方法;亦可以作为静态编译器,实现 AOT 编译。在 JDK 10 中,Graal 作为试验性 JIT compiler 一同发布
java 命令直接编译并运行 java 文件,省去先 javac 编译生成 class 再运行的步骤
以前编译一个 java 文件时,需要先 javac 编译为 class,然后再用 java 执行,现在可以放到一起执行了,香啊!
$ java HelloWorld.java
Hello Java 11!
JShell REPL
Java 9 提供了交互式解释器。有了 JShell 以后,Java 终于可以像 Python,Node.js 一样在 Shell 中运行一些代码并直接得出结果了。
Java 17
主要特性:
- 增强switch表达式, instance of,文本块支持
- 新增Record类,Sealed类,隐藏类
- 移除CMS, G1收集器优化,ZGC, Shenandoah成为正式版
switch 表达式
Java 12 以后,switch 不仅可以作为语句,也可以作为表达式。可以定义多个case标签并使用箭头返回值。
private String switchTest(int i) {
return switch (i) {
case 1 -> "1";
default -> "0";
};
}
也可以写逻辑,然后返回(13)
int j = newSwitch (day) {
case 1, 2, 3 -> "摸鱼工作日";
case 4 -> "小周五";
case 5, 6, 7 -> "休息日";
default -> {
var k = day.toString().length();
var res = f(k);
yield res;
}
};
instanceof + 类型强转一步到位
之前处理动态类型碰上要强转时,需要先 instanceof 判断一下,然后再强转为该类型处理:
Object obj = "Hello Java 12!";
if (obj instanceof String) {
String s = (String) obj;
int length = s.length();
}
现在 instanceof 支持直接类型转换了,不需要再来一次额外的强转:
Object obj = "Hello Java 12!";
if (obj instanceof String str) {
int length = str.length();
}
文本块(Text Block)的支持
文本块是多行字符串文字,它避免使用转义序列,并以可预测的方式自动设置字符串格式。它还使开发人员可以控制字符串的格式。从Java 13开始,文本块可用作预览功能。它们以三个双引号(""")开头。让我们看看我们如何轻松地创建和格式化JSON消息。
public String getNewPrettyPrintJson() {
return """
{
"name": "chenhaha",
"sex": "undefined"
}
""";
}
新增 Record 类
从Java 14开始,引入了新的Record类。我们定义Record类时,使用关键字record;
使用Records可以定义不可变的纯数据类(仅限getter),也叫记录类。它会自动创建toString,equals和hashCode方法。实际上,只需要定义如下所示的字段即可。
public record Person(String name, int age) {}
具有类似功能的类如record包含字段,构造函数,getter和toString,equals以及hashCode方法。
public class PersonOld {
private final String name;
private final int age;
public PersonOld(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersonOld personOld = (PersonOld) o;
return age == personOld.age && name.equals(personOld.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "PersonOld{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
封闭(Sealed)类
使用密封类功能,可以限制超类的使用。使用new关键字,sealed可以定义哪些其他类或接口可以扩展或实现当前类。
public abstract sealed class Pet permits Cat, Dog {}
允许的子类必须定义一个修饰符。如果不想允许任何其他扩展名,则需要使用final关键字。
public final class Cat extends Pet {}
另一方面,你可以打开扩展类。在这种情况下,应使用non-sealed修饰符。
public non-sealed class Dog extends Pet {}
当然,下面的可见声明是不允许的。
public final class Tiger extends Pet {}
隐藏类
标准 API 来定义无法发现且具有有限生命周期的隐藏类,从而提高 JVM 上所有语言的效率。JDK内部和外部的框架将能够动态生成类,而这些类可以定义隐藏类。通常来说基于JVM的很多语言都有动态生成类的机制,这样可以提高语言的灵活性和效率。
- 隐藏类天生为框架设计的,在运行时生成内部的class。
- 隐藏类只能通过反射访问,不能直接被其他类的字节码访问。
- 隐藏类可以独立于其他类加载、卸载,这可以减少框架的内存占用。
不能直接被其他class的二进制代码使用的class。隐藏类主要被一些框架用来生成运行时类,但是这些类不是被用来直接使用的,而是通过反射机制来调用。
禁用、弃用偏向锁
instanceof模式匹配、Record、jpackage打包工具开始正式使用
- instanceof的模式匹配在JDK14作为preview,在JDK15作为第二轮的preview,在JDK16转正
- Record类型在JDK14作为preview,在JDK15处于第二轮preview,在JDK16转正
- jpackage在JDK14引入,JDK15作为incubating工具,在JDK16转正,从
jdk.incubator.jpackage转为jdk.jpackage。它支持Linux: deb and rpm、macOS: pkg and dmg、Windows: msi and exe
G1 收集器优化
JDK 12 为垃圾收集器 G1 带来了两项更新
- 可中止的混合收集集合:为了达到用户提供的停顿时间目标,通过把要被回收的区域集(混合收集集合)拆分为强制和可选部分,使 G1 垃圾回收器能中止垃圾回收过程。G1 可以中止可选部分的回收以达到停顿时间目标。
- 及时返回未使用的已分配内存:由于 G1 尽量避免完整的 GC,并且仅基于 Java 堆占用和分配活动来触发并发周期,因此在许多情况下,除非从外部强制执行,否则它不会返还 Java 堆内存。JDK 12增强了 G1 GC,可以在空闲时自动将 Java 堆内存返回给操作系统。
垃圾回收器(更新优化)
- 移除 CMS(Concurrent Mark Sweep)垃圾收集器
- ZGC优化window和mac
- 弃用 ParallelScavenge + SerialOld GC 组合
ZGC 和 Shenandoah 两款垃圾回收器正式登陆
在 JAVA 15中,ZGC 和 Shenandoah 再也不是实验功能,正式登陆了(不过 G1 仍然是默认的)。如果你升级到 JAVA 15 以后的版本,就赶快试试吧,性能更强,延迟更低。
新增 jpackage 打包工具
新增 jpackage 打包工具,直接打包二进制程序,再也不用装 JRE 了!
之前如果想构建一个可执行的程序,还需要借助三方工具,将 JRE 一起打包,或者让客户电脑也装一个 JRE 才可以运行我们的 JAVA 程序。
现在 JAVA 直接内置了 jpackage 打包工具,帮助你一键打包二进制程序包,不用再乱折腾了。
参考:
女同事问我JDK8以后有哪些新特性?给我整不会了__陈哈哈的博客-CSDN博客