Java新特性: Java8、Java9、Java10

Posted by ZhouJ000 on July 30, 2019

Java 8

Oracle公司于2014年3月18日发布Java 8,新增了非常多的特性,主要有Lambda表达式、方法引用、默认方法、新的编译工具、Stream API、Date Time API等等

What’s New in JDK 8

语言特性

Lambda表达式

Lambda表达式,是Java 8的最重要新特性。Lambda表达式语法:

parameters -> expression
或
(parameters) ->{ statements; }

Lambda表达式特征:
1、可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
2、可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
3、可选的大括号:如果主体包含了一个语句,就不需要使用大括号
4、可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值

看一下不同特征下的表达式:

// 无参数,无圆括号,无大括号,无返回关键字
() -> 6

// 无类型声明,无圆括号,无大括号,无返回关键字
x -> x * 2

// 无类型声明,无大括号,无返回关键字
(x, y) -> x * 2 + y

// 无大括号,无返回关键字
(String x) -> System.out.print(s)

// 无返回关键字
(int x, int y) -> {System.out.println(x + y);}

// 全有
(int x, int y) -> {
	System.out.println(x + y);
	return x - y;
}	

Lambda表达式主要用来定义行内执行的方法类型接口,免去了使用匿名方法的麻烦

Lambda表达式变量作用域

Lambda表达式只能引用标记了final的外层局部变量,即使没有主动声明,也会认为是final的(即隐性的具有final的语义),因此所有引用的外部变量不能进行修改。并且在Lambda表达式当中不允许声明一个与局部变量同名的参数或者局部变量

扩展:
你真的理解闭包和lambda表达式吗
for循环中的lambda与闭包
彻底搞懂JS闭包各种坑

函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但可以有多个非抽象方法的接口。函数式接口可以被隐式转换为Lambda表达式

函数式接口可以使现有的函数友好地支持Lambda表达式

已有的函数式接口比如java.lang.Runnable、java.util.concurrent.Callable、java.util.Comparator等等,这些接口都被添加了@FunctionalInterface注解

方法引用

方法引用通过方法的名字来指向一个方法,可以直接引用现存的方法、Java类的构造方法或者实例对象,可以使语言的构造更紧凑简洁,减少冗余代码。语法上使用::一对冒号

可以使用在引用静态方法、引用对象的实例方法、引用某个对象的实例的方法、引用构造方法,我的理解就是:

(a) -> xxxx(a)
省略参数为
xxxx(类::方法名 或 对象::方法名)

比如:
1、Class::method或instance::method:
	cars.forEach(bmw -> Car.collide(bmw));  省略为--->  cars.forEach(Car::collide);
2、Class<T>::new:
	abcMethod(s -> new String(s));  省略为--->  abcMethod(String::new);
	(args) -> new ClassName(args)  --->  ClassName::new
interface Abc { public void aaa(String a); }
public static void abcMethod(Abc abc) { System.out.println("ok"); }	


1-a、ClassName::staticMethodName
		String::valueOf  --->  (s) -> String.valueOf(s)
		Math::pow  --->  (x, y) -> Math.pow(x, y)
1-b、ClassName::methodName
		String::compareToIgnoreCase
1'、instanceReference::methodName	
		myStrOps::strReverse1
		super::methodName
		this :: equals  --->  x -> this.equals(x)
2、Class::new
		String::new  --->  () -> new String()
		int[]::new  --->  x -> new int[x]

默认方法与静态方法

之前接口只能定义抽象方法,现在可以定义default默认方法和static静态方法

这个特性使得如果需要修改接口时,不必修改全部实现该接口的类,比如Iterable接口中新添加的默认方法forEach等。如果一个类实现的2个接口拥有相同的default方法,那么该类需要使用@Override重写该方法,当需要使用指定父类的default方法时,使用interface.super.method进行调用

default void say(){
	System.out.println("hello world");
}

Java 8提供的静态方法,也可以在接口里提供实现

static void bye(){
  System.out.println("bye");
}

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口,但在实际开发中应该谨慎使用

注解

Java 8以前,同一个地方不允许多次使用同一个注解,而现在打破了这个限制,引入了重复注解的概念。同时反射API提供了一个新的方法:getAnnotationsByType()可以返回某个类型的重复注解

Java 8还拓宽了注解的应用场景。现在注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上:

public static class Holder<@NonEmpty T> extends @NonEmpty Object {
	public void method() throws @NonEmpty Exception {   
		final Holder<String> holder = new @NonEmpty Holder<String>();        
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();    
	}
}

API库

Stream API

Java 8 API添加了一个新的抽象称为流Stream,可以使用类似管道的方式处理数据,对Java集合运算和表达的高阶抽象

Stream(流)是一个来自数据源的元素队列并支持聚合操作。Stream本身并不会存储元素,数据源流的来源,可以是集合、数组、I/O channel、生成器generator等。聚合操作有filter, map, reduce, find, match, sorted等,每个中间操作都会返回流对象本身,这样串联成一个管道,可以对操作进行优化。并且Stream通过访问者模式提供了内部迭代的方式,区别于以往的for和Iterator遍历的外部迭代方式

集合接口生成流有两种方法:
1、stream():为集合创建串行流
2、parallelStream():为集合创建并行流

操作

操作方法分为中间操作和终结操作,前者会返回流使得可以继续操作

1、forEach操作,来迭代流中的每个元素:

persons.forEach(System.out::println);

2、filter操作,通过条件过滤元素:

persons.stream().filter(person -> person.getSex().equals(1)).collect(Collectors.toList());

3、map操作,用于映射每个元素到对应的结果:

List<Integer> ages = persons.stream().map(Person::getAge).collect(Collectors.toList());

4、limit操作,限制返回元素数量:

persons.stream().limit(1).forEach(System.out::println);

5、sorted操作,对流进行排序:

persons.stream().map(Person::getAge).sorted((a, b) -> b - a).collect(Collectors.toList());

6、Collectors归约操作:

strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
Map<Integer, List<Person>> sexMap = persons.stream().collect( Collectors.groupingBy(Person::getSex));

7、统计操作:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

strings.stream().filter(string->string.isEmpty()).count();

8、并行

Integer sum = persons.parallelStream().map(Person::getAge).reduce(0, Integer::sum);

时间API

Java 8之前的时间是非线程安全的,设计上也不是很好(Date和Calendar混淆)。Java 8这次在java.time包下提供了很多新的API,吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性,如果某个实例需要修改,则返回一个新的对象

1、Clock使用时区来返回当前的纳秒时间和日期:

// System.currentTimeMillis()和TimeZone.getDefault()
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );

2、Local(本地):LocalDate和LocalTime,分别表示日期部分和时间部分,而LocalDateTime则同时包含日期和时间

final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

3、Zoned(时区):ZonedDateTime可以设置不同的时区

final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

3、格式化

LocalDateTime now = LocalDateTime.now();
DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
now.format(ofPattern);
// ofPattern.format(now);

4、使用Duration计算差值

final Duration duration = Duration.between( from, to );
System.out.println("Duration in days: " + duration.toDays());
System.out.println("Duration in hours: " + duration.toHours());

Optional

Optional类是一个可以为null的容器对象。它可以保存类型T的值,或者仅仅保存null。使得我们就不用显式进行空值检测,很好的解决空指针异常

// 可传入null
Optional<Integer> a = Optional.ofNullable(null
// 不可传入null
Optional<Integer> b = Optional.of(2);
// 过滤条件,这里使用Lambda表达式,其函数式接口有or、and等default方法
Optional<Integer> c = b.filter(num -> num % 2 == 0);
// 如果存在值则执行,这里使用Lambda表达式,其函数接口有andThen的default方法
c.ifPresent(System.out::println);
// Optional.empty
System.out.println(a);
System.out.println(a.orElse(new Integer("1")) * b.get());
System.out.println(a.orElseGet(() -> 2) * b.get());

其他

对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码

final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
// Base64.getUrlEncoder() / Base64.getUrlDecoder()
// Base64.getMimeEncoder() / Base64.getMimeDecoder()

Java8版本新增很多新的方法,用于支持并行数组处理,最重要的方法是:Arrays.parallelSort()

基于新增的lambda表达式和steam特性,Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作。Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)

工具

Nashorn JavaScript

Nashorn(Java 11后也被替代了)取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的invokedynamic,将JavaScript编译成Java字节码,与先前的Rhino实现相比,这带来了2到10倍的性能提升

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码:

jjs hello.js

或者使用jjs进入控制台
jjs
jjs>print("hello jjs")
jjs>exit()

传入参数
jjs -- a b c
print("args is: "+ arguments.join(", "))

我们也可以在Java中调用JavaScript:

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");

String name = "Runoob";
try {
	nashorn.eval("print('hello " + name + "')");
	Integer result = (Integer) nashorn.eval("10 + 2");
	System.out.println(result);
} catch(ScriptException e){
	System.out.println("执行脚本错误: "+ e.getMessage());
}

在javaScript中使用Java(jjs执行):

var BigDecimal = Java.type('java.math.BigDecimal');

类依赖分析器

jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台,例如jdeps.exe spring-boot-2.0.5.RELEASE.jar

JVM相关

编译器

为了在运行时获得Java程序中方法的参数名称,Java 8将这个特性规范化,在语言层面可以使用反射API的Parameter.getName()方法,在字节码层面使用新的javac编译器以及-parameters参数

元空间

使用Metaspace代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize

扩展:
Java8内存模型—永久代(PermGen)和元空间(Metaspace)

Java 9

Java 9发布于2017年9月22日,带来了很多新特性,其中最主要的变化是已经实现的模块化系统

What’s New in Oracle JDK 9

语言特性

Jigsaw(Modularity)

Java 9最大的变化之一是引入了模块系统(Jigsaw 项目),模块就是代码和数据的封装体。模块的代码被组织成多个包,每个包中包含Java类和接口;模块的数据则包括资源文件和其他静态信息

Java 9模块的重要特征是在其工件(artifact)的根目录中包含了一个描述模块的module-info.class文件。工件的格式可以是传统的JAR文件或是Java 9新增的JMOD文件。这个文件由根目录中的源代码文件module-info.java编译而来,声明文件可以描述模块的不同特征

在module-info.java文件中,可以用新的关键词module来声明一个模块,在idea中已经完美支持了模块化:

module com.hello {
    requires com.bye;
    exports com.alpha;
    exports com.beta;
}

module com.good {
    requires com.hello;
}

// 如果报红,导入一下后,就可以在com.good模块中就可以使用com.hello模块中导出的com.alpha和com.beta包下的类了

私有接口方法

在Java 8接口引入了默认方法和静态方法,Java 9在此之上加入了私有方法和私有静态方法

private void a() {
	// ...
}
private static void d() {
	// ...
}

我觉得这些新增方法的主要作用就是可以将多个默认方法和静态方法的共有逻辑抽出来

改进的try-with-resources

try-with-resources是JDK 7中一个新的异常处理机制,可以省略在finally写关闭逻辑,确保资源(实现了AutoCloseable接口)在结束时倍关闭,然而其需要定义在try()中。在java 9中进行了改进,如果你已经有一个资源是final或等效于final的变量,那可以在try-with-resources中直接使用,而无需重新声明一个新的变量

try(myFile){
	myFile.load();
} catch (Exception e) {
	e.printStackTrace();
}

API库

Flow API(Reactive Streams)

Java 9 Reactive Streams允许我们实现非阻塞异步流处理,这是将响应式编程模型应用于核心java编程的重要一步,其通过java.util.concurrent.Flow API引入了响应流支持

Reactive Streams是关于流的异步处理,因此应该有一个发布者(Publisher)和一个订阅者(Subscriber)。发布者发布数据流,订阅者使用数据 flow1

有时我们必须在Publisher和Subscriber之间转换数据。处理器(Processor)是位于最终发布者和订阅者之间的实体,用于转换从发布者接收的数据,以便订阅者能理解它。我们可以拥有一系列(chain)处理器 flow2

Flow API是Iterator和Observer模式的组合。Iterator在pull模型上工作,用于应用程序从源中拉取项目;而Observer在push模型上工作,并在item从源推送到应用程序时作出反应

  • java.util.concurrent.Flow:是Flow API的主要类,封装了Flow API的所有重要接口。而且这是一个final类,我们不能扩展它
  • java.util.concurrent.Flow.Publisher:功能接口,每个发布者必须实现它的subscribe方法,并添加相关的订阅者以接收消息
    • java.util.concurrent.SubmissionPublisher:Publisher的一个实现,它将提交的项目异步发送给当前订阅者,直到它关闭为止。它使用Executor框架
  • java.util.concurrent.Flow.Subscriber:每个订阅者必须实现该接口
    • onSubscribe:订阅者订阅了发布者后调用的第一个方法
    • onNext:从发布者收到项目时调用,是我们实现业务逻辑的处理流
    • onError:当发生不可恢复的错误时调用
    • onComplete:像finally方法一样,在发布者没有发布其他项目或发布者关闭时调用
  • java.util.concurrent.Flow.Subscription:用于在发布者和订阅者之间创建异步非阻塞链接。订阅者调用其request方法来向发布者请求项目,或者使用cancel取消订阅
  • java.util.concurrent.Flow.Processor:此接口同时扩展了Publisher和Subscriber接口,用于在发布者和订阅者之间转换消息

主要逻辑代码:

class MySubscriber implements Flow.Subscriber<Employee> {
	private Flow.Subscription subscription;
	
	@Override
    public void onSubscribe(Flow.Subscription subscription) {
        this.subscription = subscription;
		// requesting data from publisher
        this.subscription.request(1); 
    }
	@Override
    public void onNext(Employee item) {
        System.out.println("Processing Employee "+item);
		// requesting next data from publisher
        this.subscription.request(1);
    }
	// ...
}

// main
// Create Publisher
SubmissionPublisher<Employee> publisher = new SubmissionPublisher<>();

// Register Subscriber
MySubscriber subs = new MySubscriber();
publisher.subscribe(subs);
publisher.submit(employee);
// ...
// close the Publisher
publisher.close();

扩展:
Reactive Streams 的实现原理

集合工厂方法

Java 9有新的静态工厂方法可以创建List、Set和Map集合的不可变实例,这些工厂方法可以以更简洁的方式来创建集合

Set<String> set = Set.of("A", "B", "C");
List<String> list = List.of("A", "B", "C");
Map<String, String> map = Map.of("A","Apple","B","Boy","C","Cat");
Map<String, String> map1 = Map.ofEntries (
                new AbstractMap.SimpleEntry<>("A","Apple"),
                new AbstractMap.SimpleEntry<>("B","Boy"),
                new AbstractMap.SimpleEntry<>("C","Cat"));

改进的Process API

Java 9向Process API添加了一个ProcessHandle接口来增强java.lang.Process类,ProcessHandle接口的实例标识一个本地进程,它允许查询进程状态并管理进程

// mspaint
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
Process p = pb.start();
ProcessHandle.Info info = p.info();
System.out.printf("Process ID : %s%n", p.pid());
System.out.printf("Command name : %s%n", info.command().orElse("Not Present"));
System.out.printf("User : %s%n", info.user().orElse("Not Present"));
System.out.printf("Start time: %s%n",
                info.startInstant().map(i -> i.atZone(ZoneId.systemDefault()).toLocalDateTime()).get());

改进的Stream API

Java 9为Stream新增了几个方法:dropWhile、takeWhile、ofNullable,为iterate方法新增了一个重载方法。使流处理更容易,并使用收集器编写复杂的查询

1、takeWhile,返回给定Stream的子集直到断言条件返回false:

Stream.of("a", "b", "c", "", "e", "f").takeWhile(s -> !s.isEmpty()).forEach(System.out::print);

2、dropWhile,直到断言条件返回true,开始返回给定Stream后面的子集

Stream.of("a", "b", "c", "", "e", "f").dropWhile(s -> !s.isEmpty()).forEach(System.out::print);

3、iterate,按照类似for循环的条件,从初始种子开始创建顺序流

IntStream.iterate(0, x -> x < 10, x -> x + 2).forEach(System.out::println);

工具

Jshell

REPL(Read Eval Print Loop)意为交互式的编程环境。JShell是Java 9新增的一个交互式的编程环境工具。它允许你无需使用类或者方法包装来执行Java语句。它与 Python的解释器类似,可以直接输入表达式并查看其执行结果

简而言之,它允许您将独立的Java代码片段写入控制台(READ),立即执行它们(EVAL),然后查看结果(PRINT)并继续记住您已编写的内容(LOOP)。如果您想快速尝试一段代码,草拟算法,检查某些方法如何处理异常输入,为您的博客帖子创建和测试代码片段,它是一个完美的工具。您只需快速尝试一些一次性代码并立即看到结果。最好的部分是 - 它不需要大多数Java样板

jshell
// 查看描述
jshell> /help intro
// 查看帮助
jshell> /help

jshell> System.out.println("jshell")
jshell

jshell> int add(int a, int b) {
   ...> return a + b;
   ...> }
|  已创建 方法 add(int,int)

jshell> add(1, 3)
$2 ==> 4

JVM相关

默认使用G1作为垃圾回收器

扩展:
可能是最全面的 Java G1学习笔记
深入理解Java G1垃圾收集器
java G1 垃圾收集器解析

Java 10

北京时间3月21日,Oracle官方宣布Java 10正式发布,(Java 9和Java 10都不是LTS版本,只有半年左右的开发和维护期。而未来的Java 11才是Java 8之后第一个LTS版本)

JDK 10 Release Notes

语言特性

局部变量类型推断

可以类似于js的写法,是Java增加一些语法糖:

var list = new ArrayList<>();
list.add(1);
list.add("a");
list.add(true);
list.forEach(System.out::println);

局部变量类型推荐仅限于如下使用场景:
1、局部变量初始化 2、for循环内部索引变量
3、传统的for循环声明变量

Java官方表示,它不能用于以下几个地方:
1、方法参数
2、构造函数参数
3、方法返回类型
4、字段
5、捕获表达式(或任何其他类型的变量声明)

JVM相关

G1的并行FUll GC

G1是设计来作为一种低延时的垃圾回收器,是被设计用来避免JVM进行Full GC操作的,但是当并发收集器回收内存太慢(跟不上旧的堆碎片产生的提升速率)时JVM就会进行一次Full GC,之前G1垃圾收集器在Full GC时使用的是单线程的标记-清除-整理算法

在G1作为JDK9版本中默认使用以前,默认的垃圾收集器已经有了并行化的Full GC功能。因此在这个版本,G1采用完全GC并行,并行化标记-清除-整理算法,使用的线程数和在年轻代和混合收集使用的线程数量一致。线程数量可以通过-XX:ParallelGCThreads参数来配置,这个配置同时也会影响在年轻代和混合代垃圾收集时的线程数量,来改善G1最坏情况的等待时间