Java8、Java9、Java10新特性学习笔记

Java 的新版本发布节奏

过去,JDK 版本发布节奏受重大新特性所驱动。以最近为例,Java 8 以 lambda 表达式和流的形式引入了函数式编程,Java 9 引入了模块化 Java 系统。每个新版本都被热切期待,而一些小的修复补丁则常常被搁在一边,等待更大组件的最终确定。Java 的演变落后于其他语言。

新的高频率节奏促使 Java 在有较小的改进时就发布新版本。在发布日之前将已准备就绪的特性包含在新版本内,未准备就绪的特性可以计划包含在 6 个月后的下一个版本中。这个新周期中的第一个 Java 版本是 Java 9,它于 2017 年 10 月推出。Java 10 于 2018 年 3 月发布,Java 11 预计将在 2018 年 9 月发布。

作为新节奏的一部分,Oracle 表示它对每个主要版本的支持仅持续到下一个主要版本推出之前。当 Java 11 发布时,Oracle 将停止支持 Java 10。如果开发人员想要确保其 Java 版本受到支持,则需要每隔 6 个月就迁移到一个主要版本。不想要或不需要如此频繁地进行迁移的开发人员,可以使用 LTS(长期支持)版本,该版本每 3 年更新一次。对当前的 LTS 版本 (Java 8) 的支持将截止于今年秋天推出 Java 11 时。

JDK8新特性

  1. Lambda 表达式
  2. 新的日期时间 API
  3. Optional
  4. Base64
  5. HashMap的改进
  6. 接口的默认方法和静态方法
  7. Stream

Lambda表达式

这里不重点介绍Lambda表达式,只是简单介绍。

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

什么时候能用Lambda表达式?

先说一个名词的概念

函数式接口:Functional Interface. 
定义的一个接口,接口里面必须 有且只有一个抽象方法 ,这样的接口就成为函数式接口。 
在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。 
JAVA8的接口可以有多个default方法

任何函数式接口都可以使用lambda表达式替换。 
例如:ActionListener、Comparator、Runnable

lambda表达式只能出现在目标类型为函数式接口的上下文中。

注意: 
此处是只能!!! 
意味着如果我们提供的这个接口包含一个以上的Abstract Method,那么使用lambda表达式则会报错。 
这点已经验证过了。

场景: 

这种场景其实很常见: 
你在某处就真的只需要一个能做一件事情的函数而已,连它叫什么名字都无关紧要。 
Lambda 表达式就可以用来做这件事。

lambda表达式本质上是一个匿名方法。让我们来看下面这个例子:

public int add(int x, int y) {
    return x + y;
}

转成lambda表达式后是这个样子:

(int x, int y) -> x + y;

参数类型也可以省略,Java编译器会根据上下文推断出来:

(x, y) -> x + y; //返回两数之和

或者

(x, y) -> { return x + y; } //显式指明返回值

可见lambda表达式有三部分组成:

  • 参数列表
  • 箭头(->)
  • 表达式或语句块。

下面这个例子里的lambda表达式没有参数,也没有返回值(相当于一个方法接受0个参数,返回void,其实就是Runnable里run方法的一个实现):

() -> { System.out.println("Hello Lambda!"); }

如果只有一个参数且可以被Java推断出类型,那么参数列表的括号也可以省略:

c -> { return c.size(); }

用lambda表达式实现Runnable

我开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。看一下Java 8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码。我们在这里做了什么呢?那就是用() -> {}代码块替代了整个匿名类。

Java8之前

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Before Java8, too much code for too little to do");
    }
}).start();

Java8

//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

日期

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

具体请看我另一篇博文

Optional

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 类的引入很好的解决空指针异常。

Optional 实例

我们可以通过以下实例来更好的了解 Optional 类的使用:

public static void main(String args[]) {
    Integer value1 = null;
    Integer value2 = new Integer(10);
    // 允许传递为null参数
    Optional<Integer> a = Optional.ofNullable(value1);
    // 如果传递的参数是null,抛出异常NullPointerException
    Optional<Integer> b = Optional.of(value2);
    System.out.println(sum(a, b));
}
public Integer sum(Optional<Integer>a, Optional<Integer>b) {
    // 判断值是否存在
    System.out.println("第一个参数值存在:" + a.isPresent());
    System.out.println("第二个参数值存在:" + b.isPresent());
    // 如果值存在,返回它,否则返回默认值
    Integer value1 = a.orElse(new Integer(0));
    // 获取值,值需要存在
    Integer value2 = b.get();
    return value1 + value2;
}

执行以上脚本,输出结果为:

第一个参数值存在: false
第二个参数值存在: true
10

Base64

在Java 8中,Base64编码已经成为Java类库的标准。

Java 8 内置了 Base64 编码的编码器和解码器。

Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

  • 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
  • URL输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
  • MIME输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。

Base64 实例

以下实例演示了 Base64 的使用:

@Test
public void base64Test() throws UnsupportedEncodingException {
    // 使用基本编码
    String base64encodedString = Base64.getEncoder().encodeToString("java@crankz".getBytes("utf-8"));
    System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);
    // 解码
    byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
    System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
}

结果

Base64 编码字符串 (基本) :amF2YUBjcmFua3o=
原始字符串: java@crankz

HashMap的改进

具体参考我的另一篇博文

接口的默认方法和静态方法

Java 8 新增了接口的默认方法。

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

我们只需在方法名前面加个default关键字即可实现默认方法。

为什么要有这个特性?

以前当需要修改接口时候,需要修改全部实现该接口的类。

所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

默认方法语法格式如下:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}

多个默认方法

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}
public interface FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   default void print(){
      System.out.println("我是一辆四轮汽车!");
   }
}

第二种解决方案可以使用 super 来调用指定接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   public void print(){
      Vehicle.super.print();
   }
}

静态默认方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:

public interface Vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
   // 静态方法
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}

我们综合上面讲的,实战一下  

@Test
public void interfaceDefaultTest() {
    Vehicle vehicle = new Car();
    vehicle.print();
}

interface Vehicle {
    default void print() {
        System.out.println("我是一辆车!");
    }

    static void blowHorn() {
        System.out.println("按喇叭!!!");
    }
}

interface FourWheeler {
    default void print() {
        System.out.println("我是一辆四轮车!");
    }
}

class Car implements Vehicle, FourWheeler {
    public void print() {
        Vehicle.super.print();
        FourWheeler.super.print();
        Vehicle.blowHorn();
        System.out.println("我是一辆汽车!");
    }
}

执行以上脚本,输出结果为:

我是一辆车!
我是一辆四轮车!
按喇叭!!!
我是一辆汽车!

Stream

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

所以这边有两个概念

  • 管道

Stream API可以提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

这里有两个操作

  • 中间操作
  • 最终操作

操作举例:

List<Integer> transactionsIds =
widgets.stream()
    .filter(b -> b.getColor() == RED)
    .sorted((x,y) -> x.getWeight() - y.getWeight())
    .mapToInt(Widget::getWeight)
    .sum();

在 Java 8 中, 集合接口有两个方法来生成流:

  1. stream():为集合创建串行流。
  2. parallelStream():为集合创建并行流。

forEach

Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

@Test
public void streamRandomTest() {
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
}

中间操作

map(映射)

map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

@Test
public void streamMapTest() {
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    // 获取对应的平方数
    List<Integer> squaresList = numbers.stream()
            .map(i -> i * i)
            .distinct()
            .collect(Collectors.toList());

    for (int x : squaresList) {
        System.out.println(x);
    }
}

Filter(过滤)

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

@Test
public void streamFilterTest() {
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    // 获取空字符串的数量
    long count = strings.stream().filter(string -> string.isEmpty()).count();
    System.out.println(count);
}

Limit(限制)

imit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

@Test
public void streamLimitTest() {
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
}

Sorted(排序)

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

@Test
public void streamSortedTest() {
    Random random = new Random();
    random.ints().limit(10).sorted().forEach(System.out::println);
}

最终操作

Match(匹配)

用来判断某个predicate是否和流对象相匹配,最终返回Boolean类型结果,例如:

@Test
public void streamMatchTest() {
    List<String> list = new ArrayList<String>();
    list.add("a1");
    list.add("b1");

    // 流对象中只要有一个元素匹配就返回true
    boolean anyStartWithA = list.stream().anyMatch((s -> s.startsWith("a")));
    System.out.println(anyStartWithA);
    // 流对象中每个元素都匹配就返回true
    boolean allStartWithA = list.stream().allMatch((s -> s.startsWith("a")));
    System.out.println(allStartWithA);
}

Collectors(收集)

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

@Test
public void streamCollectorsTest() {
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    System.out.println("筛选列表: " + filtered);
    String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
    System.out.println("合并字符串: " + mergedString);
}

统计

另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

@Test
public void streamCollectorsTest2() {
    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());
}

Count(计数)

类似sql的count,用来统计流中元素的总数,例如:

@Test
public void streamCountTest() {
    List<String> strings = Arrays.asList("a", "b", "c");
    long count = strings.stream().filter((s -> s.startsWith("a"))).count();
    System.out.println(count);
}

Reduce(规约)

reduce方法允许我们用自己的方式去计算元素或者将一个Stream中的元素以某种规律关联,例如:

@Test
public void streamReduceTest() {
    List<String> strings = Arrays.asList("a", "b", "c");
    strings.stream().sorted().reduce((s1, s2) -> {
        System.out.println(s1 + "|" + s2);
        return s1 + "|" + s2;
    });
}

执行结果如下:

a|b
a|b|c

parallel(并行)

parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:

@Test
public void parallelStreamTest() {
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    // 获取空字符串的数量
    long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
    System.out.println(count);
}

我们可以很容易的在顺序运行和并行直接切换。

并行Stream VS 串行Stream

到目前我们已经将常用的中间操作和完结操作介绍完了。当然所有的的示例都是基于串行Stream。接下来介绍重点戏——并行Stream(parallel Stream)。并行Stream基于Fork-join并行分解框架实现,将大数据集合切分为多个小数据结合交给不同的线程去处理,这样在多核处理情况下,性能会得到很大的提高。这和MapReduce的设计理念一致:大任务化小,小任务再分配到不同的机器执行。只不过这里的小任务是交给不同的处理器。

通过parallelStream()创建并行Stream。为了验证并行Stream是否真的能提高性能,我们执行以下测试代码:

首先创建一个较大的集合:

@Test
public void parallelCompare() {
    // 创建一个较大的集合
    List<String> bigLists = new ArrayList<>();
    for (int i = 0; i < 10000000; i++) {
        UUID uuid = UUID.randomUUID();
        bigLists.add(uuid.toString());
    }
    notParallelStreamSortedTest(bigLists);
    parallelStreamSortedTest(bigLists);
}

// 不使用并行操作
private void notParallelStreamSortedTest(List<String> bigLists) {
    long startTime = System.nanoTime();
    long count = bigLists.stream().sorted().count();
    long endTime = System.nanoTime();
    long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
    System.out.println(System.out.printf("串行排序: %d ms", millis));
}

// 使用并行操作
private static void parallelStreamSortedTest(List<String> bigLists) {
    long startTime = System.nanoTime();
    long count = bigLists.parallelStream().sorted().count();
    long endTime = System.nanoTime();
    long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
    System.out.println(System.out.printf("并行排序: %d ms", millis));
}

结果如下:

串行排序: 13053
并行排序: 5559

看到这里,我们确实发现性能提高了约么50%,你也可能会想以后都用parallel Stream不久行了么?实则不然,如果你现在还是单核处理器,而数据量又不算很大的情况下,串行流仍然是这种不错的选择。你也会发现在某些情况,串行流的性能反而更好,至于具体的使用,需要你根据实际场景先测试后再决定。

JDK9新特性

  1. 模块系统
  2. JShell:交互式Java REPL
  3. 不可变集合类工厂方法
  4. 接口中的私有方法
  5. 进程API改进
  6. try-with-resources改进
  7. HTTP2支持
  8. 平台日志API和服务
  9. 垃圾回收器
  10. 增加加密算法

模块系统

Java 9 新特性中最大的一个变化就是 Module System。

JDK 9 包含有 92 个 modules(当然也可能在最终发布版中有所变化)。我们可以使用 JDK Modules,也能创建我们自己的 modules,如:

简单 Module 示例

module com.foo.bar { }

这里我们使用 module 关键字创建了一个简单的 module。

每个 module 都有一个名字,对应代码和其它一些资源。

JShell: 交互式 Java REPL

REPL是一种快速运行语句的命令行工具。

类似Python IDLE、windows cmd

G:\>jshell
|  Welcome to JShell -- Version 9-ea
|  For an introduction type: /help intro
jshell> int a = 10
a ==> 10
jshell> System.out.println("a value = " + a )
a value = 10

不可变集合类的工厂方法

Oracle 公司引入一些方便使用的工厂方法,用于创建不可变集合 List,Set,Map 和 Map.Entry 对象。这些高效实用的方法可用来创建空或者非空集合对象。

在 Java SE 8 和更早版本中,我们常用类似 unmodifiableXXX 的集合类方法创建不可变集合对象。举个例子,比如我们想创建一个不可变的 List 对象,可能使用到 Collections.unmodifiableList 方法。

然而,这些 Collections.unmodifiableXXX 方法显得非常冗长乏味。为了克服这些缺陷,Oracle 公司给 List、Set 和 Map 接口分别添加了两个更加实用的方法。

List 和 Set 接口使用 of() 方法创建一个空或者非空的不可变 List 或 Set 对象,如:

空 List 示例

List immutableList = List.of();

非空 List 示例

List immutableList = List.of("one","two","three");

接口中的私有方法

在 Java 8 中,我们可以在接口中使用默认或者静态方法提供一些实现方式,但是不能创建私有方法。

为了避免冗余代码和提高重用性,Oracle 公司在 Java 9 接口中引入私有方法。

也就是说从 Java 9 开始,我们也能够在接口类中使用 ‘private’ 关键字写私有化方法和私有化静态方法。

接口中的私有方法与 class 类中的私有方法在写法上并无差异,如:

public interface Card{
  private Long createCardID(){
    // Method implementation goes here.
  }
  private static void displayCardDetails(){
    // Method implementation goes here.
  }
}

进程API的改进

Java 9 迎来一些 Process API 的改进,通过添加一些新的类和方法来优化系统级进程的管控。

Process API 中的两个新接口:

java.lang.ProcessHandle
java.lang.ProcessHandle.Info

Process API 示例

ProcessHandle currentProcess = ProcessHandle.current();
System.out.println("Current Process Id: = " + currentProcess.getPid());

Try-With-Resources 改进

我们知道,Java 7 引入了一个新的异常处理结构:Try-With-Resources,来自动管理资源。这个新的声明结构主要目的是实现“Automatic Better Resource Management”(“自动资源管理”)。

Java 9 将对这个声明作出一些改进来避免一些冗长写法,同时提高可读性。

Java 7 示例

void testARM_Before_Java9() throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
    try (BufferedReader reader2 = reader1) {
        System.out.println(reader2.readLine());
    }
}

Java 9 示例

void testARM_Java9() throws IOException {
    BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
    try (reader1) {
        System.out.println(reader1.readLine());
    }
}

HTTP2支持

在 Java 9 中,Oracle 公司将发布新的 HTTP 2 Client API 来支持 HTTP/2 协议和 WebSocket 特性。

现有的 HTTP Client API 存在很多问题(如支持 HTTP/1.1 协议但是不支持 HTTP/2 协议和 WebSocket,仅仅作用在 Blocking 模式中,并存在大量性能问题),他们正在被使用新的 HTTP 客户端的 HttpURLConnection API 所替代。

HTTP 2 Client 示例

jshell> import java.net.http.*
jshell> import static java.net.http.HttpRequest.*
jshell> import static java.net.http.HttpResponse.*
jshell> URI uri = new URI("http://rams4java.blogspot.co.uk/2016/05/java-news.html")
uri ==> http://rams4java.blogspot.co.uk/2016/05/java-news.html
jshell> HttpResponse response = HttpRequest.create(uri).body(noBody()).GET().response()
response ==> java.net.http.HttpResponseImpl@79efed2d
jshell> System.out.println("Response was " + response.body(asString()))

平台日志 API 和 服务

Java 9 中 ,JVM 有了统一的日志记录系统,可以使用新的命令行选项-Xlog 来控制 JVM 上 所有组件的日志记录。该日志记录系统可以设置输出的日志消息的标签、级别、修饰符和输出目标等。

Java 9 允许为 JDK 和应用配置同样的日志实现。新增的 System.LoggerFinder 用来管理 JDK 使 用的日志记录器实现。JVM 在运行时只有一个系统范围的 LoggerFinder 实例。LoggerFinder 通 过服务查找机制来加载日志记录器实现。默认情况下,JDK 使用 java.logging 模块中的 java.util.logging 实现。通过 LoggerFinder 的 getLogger()方法就可以获取到表示日志记录器的 System.Logger 实现。应用同样可以使用 System.Logger 来记录日志。这样就保证了 JDK 和应用使用同样的日志实现。我们也可以通过添加自己的 System.LoggerFinder 实现来让 JDK 和应用使用 SLF4J 等其他日志记录框架。 代码清单 9 中给出了平台日志 API 的使用示例。

清单 9.使用平台日志 API

private static final System.Logger LOGGER = System.getLogger("Main");
public static void main(final String[] args) {
    LOGGER.log(Level.INFO, "Run!");
}

垃圾回收器

JDK1.8 默认垃圾收集器是Parallel

Java 9 移除了在 Java 8 中 被废弃的垃圾回收器配置组合,同时 把 G1 设为默认的垃圾回收器实现。
另外,CMS 垃圾回收器已经被声明为废弃。

增加加密算法

Java 9 新增了 4 个 SHA- 3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 S HA3-512。另外也增加了通过 java.security.SecureRandom 生成使用 DRBG 算法的强随机数。 代码清单 13 中给出了 SHA-3 哈希算法的使用示例。

public static void main(final String[] args) throws NoSuchAlgorithmException {
    final MessageDigest instance = MessageDigest.getInstance("SHA3-224");
    final byte[] digest = instance.digest("".getBytes());
    System.out.println(Hex.encodeHexString(digest));
}

Java10新特性

  1. 局部变量推断
  2. 整个JDK代码仓库
  3. 统一的垃圾回收接口
  4. 并行垃圾回收器G1
  5. 线程局部管控

局部变量推断

它向   Java   中引入在其他语言中很常见的  var   ,比如   JavaScript   。只要编译器可以推断此种类型,你不再需要专门声明一个局部变量的类型。

开发者将能够声明变量而不必指定关联的类型。比如:

List <String> list = new ArrayList <String>();
Stream <String> stream = getStream();

它可以简化为:

var list = new ArrayList();
var stream = getStream();

局部变量类型推断将引入“ var ”关键字的使用,而不是要求明确指定变量的类型,我们俗称“语法糖”。

这就消除了我们之前必须执行的 ArrayList<String> 类型定义的重复。

其实我们在JDK7,我们需要:

List <String> list = new ArrayList <String>();

但是在JDK8,我们只需要:

List <String> list = new ArrayList <>();

所以这是一个逐步的升级。也是人性化的表现与提升。

有趣的是,需要注意 var 不能成为一个关键字,而是一个保留字。这意味着你仍然可以使用 var 作为一个变量,方法或包名,但是现在(尽管我确定你绝不会)你不能再有一个类被调用。

局部变量类型推荐仅限于如下使用场景:

  • 局部变量初始化
  • for循环内部索引变量
  • 传统的for循环声明变量

Java官方表示,它不能用于以下几个地方:

  • 方法参数
  • 构造函数参数
  • 方法返回类型
  • 字段
  • 捕获表达式(或任何其他类型的变量声明)

注意:

Java的var和JavaScript的完全不同,不要这样去类比。Java的var是用于局部类型推断的,而不是JS那样的动态类型,所以下面这个样子是不行的:

var a = 10;
a = "abc"; //error!

其次,这个var只能用于局部变量声明,在其他地方使用都是错误的。

class C {
    public var a = 10; //error
    public var f() { //error
        return 10;
    }
}

整合 JDK 代码仓库

为了简化开发流程,Java 10 中会将多个代码库合并到一个代码仓库中。

在已发布的 Java 版本中,JDK 的整套代码根据不同功能已被分别存储在多个 Mercurial 存储库,这八个 Mercurial 存储库分别是:root、corba、hotspot、jaxp、jaxws、jdk、langtools、nashorn。

虽然以上八个存储库之间相互独立以保持各组件代码清晰分离,但同时管理这些存储库存在许多缺点,并且无法进行相关联源代码的管理操作。其中最重要的一点是,涉及多个存储库的变更集无法进行原子提交 (atomic commit)。例如,如果一个 bug 修复时需要对独立存储两个不同代码库的代码进行更改,那么必须创建两个提交:每个存储库中各一个。这种不连续性很容易降低项目和源代码管理工具的可跟踪性和加大复杂性。特别是,不可能跨越相互依赖的变更集的存储库执行原子提交这种多次跨仓库的变化是常见现象。

为了解决这个问题,JDK 10 中将所有现有存储库合并到一个 Mercurial 存储库中。这种合并的一个次生效应是,单一的 Mercurial 存储库比现有的八个存储库要更容易地被镜像(作为一个 Git 存储库),并且使得跨越相互依赖的变更集的存储库运行原子提交成为可能,从而简化开发和管理过程。虽然在整合过程中,外部开发人员有一些阻力,但是 JDK 开发团队已经使这一更改成为 JDK 10 的一部分。

统一的垃圾回收接口

在当前的 Java 结构中,组成垃圾回收器(GC)实现的组件分散在代码库的各个部分。尽管这些惯例对于使用 GC 计划的 JDK 开发者来说比较熟悉,但对新的开发人员来说,对于在哪里查找特定 GC 的源代码,或者实现一个新的垃圾收集器常常会感到困惑。更重要的是,随着 Java modules 的出现,我们希望在构建过程中排除不需要的 GC,但是当前 GC 接口的横向结构会给排除、定位问题带来困难。

为解决此问题,需要整合并清理 GC 接口,以便更容易地实现新的 GC,并更好地维护现有的 GC。Java 10 中,hotspot/gc 代码实现方面,引入一个干净的 GC 接口,改进不同 GC 源代码的隔离性,多个 GC 之间共享的实现细节代码应该存在于辅助类中。这种方式提供了足够的灵活性来实现全新 GC 接口,同时允许以混合搭配方式重复使用现有代码,并且能够保持代码更加干净、整洁,便于排查收集器问题。

并行垃圾回收器 G1

大家如果接触过 Java 性能调优工作,应该会知道,调优的最终目标是通过参数设置来达到快速、低延时的内存垃圾回收以提高应用吞吐量,尽可能的避免因内存回收不及时而触发的完整 GC(Full GC 会带来应用出现卡顿)。

G1 垃圾回收器是 Java 9 中 Hotspot 的默认垃圾回收器,是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是当并发收集无法快速回收内存时,会触发垃圾回收器回退进行 Full GC。之前 Java 版本中的 G1 垃圾回收器执行 GC 时采用的是基于单线程标记扫描压缩算法(mark-sweep-compact)。为了最大限度地减少 Full GC 造成的应用停顿的影响,Java 10 中将为 G1 引入多线程并行 GC,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。

Java 10 中将采用并行化 mark-sweep-compact 算法,并使用与年轻代回收和混合回收相同数量的线程。具体并行 GC 线程数量可以通过:-XX:ParallelGCThreads 参数来调节,但这也会影响用于年轻代和混合收集的工作线程数。

线程局部管控

在已有的 Java 版本中,JVM 线程只能全部启用或者停止,没法做到对单独某个线程的操作。为了能够对单独的某个线程进行操作,Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程。通过这种方式显著地提高了现有 JVM 功能的性能开销,并且改变了到达 JVM 全局安全点的现有时间语义。

增加的参数为:-XX:ThreadLocalHandshakes (默认为开启),将允许用户在支持的平台上选择安全点。

参考:

https://www.oschina.net/translate/109-new-features-in-jdk-10?cmp

https://www.ibm.com/developerworks/cn/java/j-5things17/index.html

https://www.ibm.com/developerworks/cn/java/the-new-features-of-Java-10/index.html

https://www.sojson.com/blog/279.html

https://samuelgarciastk.github.io/68756e1e/

https://yifeng.studio/2017/03/12/translation-java-9-features-with-examples/

http://www.runoob.com/java/java8-new-features.html

https://blog.csdn.net/ioriogami/article/details/12782141

猜你喜欢

转载自blog.csdn.net/CrankZ/article/details/81355643