从 JDK8 之后,Java 的更新策略改为以时间驱动的方式,每六个月发布一个新的Java版本,每三年发表一个长期支持版本。一般如果要对旧 JDK 进行升级,都会选择长期支持版,JDK11 和最近更新的 JDK17 是长期支持版本。但是由于商业项目更看重稳定性,更新 JDK 带来的收益不大,大多数人不愿意踩坑去更新 JDK。因此,很多人都只是从新闻了解到新 JDK 的新特性,平常开发没有接触到,甚至有些在用 JDK8 的人连 JDK8 的新特性都用不利索或者直接就不知道。其实许多新特性是可以简化我们的开发,能让我们以更优雅的方式实现功能。接下来我将分三篇文章分别简单介绍 JDK8、JDK9-JDk11 和 JDK12-JDK17 在编码方面的一些新功能,至于虚拟机的改进和其他部门这里暂不做讨论。

先从 Java8 开始说起,看看部分常用的新特性。
本文源码地址:code-note

1. Lambda 表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),让匿名内部类的写法更简便。

例子:

public class LambdaTest {

    public LambdaTest() {
    }
    public LambdaTest(String str) {
        //use param str to do something
    }

    public static void interfaceTest(SingleFncInterface singleFunInterface) {
        singleFunInterface.doSomething("123");
    }

    public void simpleMenthod(String str) {
        System.out.println("simple method. str is:");
    }

    public static void staticMenthod(String str) {
        System.out.println("static menthod. str is:");
    }
}
/**
 * 单函数接口
 */
@FunctionalInterface
interface SingleFncInterface {
    void doSomething(String str);

    default void print() {
        System.out.println("default method.");
    }
}

以上 SingleFncInterface 是一个典型的函数式接口,只包含一个抽象方法,可以加上 @FunctionalInterface 注解标记,限制只允许定义一个抽象方法。

/**
 * Lambda 本质就是单函数接口
 */
@Test
public void singleFunTest() {
    //作为参数的形式
    LambdaTest.interfaceTest((String str) -> {
        System.out.println("single function interface. param:" + str);
    });

    //SingleFncInterface s = (String str) -> System.out.println(str);
	SingleFncInterface s = str -> System.out.println(str);
    s.doSomething("123");

    //简化形式,方法引用
    //LambdaTest.interfaceTest(item -> System.out.println(item));
    LambdaTest.interfaceTest(System.out::println);
}

可以看出 lambda 表达式的语法格式是如:(parameters) -> expression/statements,特殊的还有更加简化的方法引用方式。

方法引用可分为三种,静态、实例和构造引用,使用例子如下:

/**
 * 方法引用
 */
@Test
public void refTest() {
    //静态引用。意思就是用 String 的 valof() 方法来实现 Function 接口的 apply 方法
    Function<Integer, String> fun = String::valueOf;
    String apply = fun.apply(100);
    System.out.println(apply);

    //静态引用
    SingleFncInterface sfi1 = LambdaTest::staticMenthod;

    //实例引用
    LambdaTest lambdaTest = new LambdaTest();
    SingleFncInterface sfi2 = lambdaTest::simpleMenthod;

    //构造引用,带参数
    SingleFncInterface sfi3 = LambdaTest::new;

    //构造引用,不带参数
    Runnable runnable = LambdaTest::new;
    //runnable.run();//单函数 Runnable 接口 run 方法由 LambdaTest 构造实现
}

根据输入和返回参数的不同,JDK 中提供了四种类型的函数式接口:

/**
 * 四种类型函数式接口
 */
@Test
public void funTest() {
    /**
     * Function<T, R>
     * 调用方法 R apply(T t);
     * T:入参类型,R:出参类型
     */
    Function<Integer, Integer> function = n -> n*n;
    Integer apply = function.apply(10);
    System.out.println(apply);

    /**
     * Consumer<T>
     * 调用方法:void accept(T t);
     * T:入参类型;没有出参
     */
    Consumer<String> consumer = System.out::println;
    consumer.accept("output msg.");

    /**
     * Supplier<T>
     * 调用方法:T get();
     * T:出参类型;没有入参
     */
    Supplier<Integer> supplier = () -> 10*10;
    Integer integer = supplier.get();
    System.out.println(integer);

    /**
     * Predicate<T>
     * 调用方法:boolean test(T t);
     * T:入参类型;出参类型是Boolean
     */
    Predicate<Integer> predicate = num -> num>10;//是否大于10
    boolean test = predicate.test(20);
    System.out.println(test);
}

2. 接口默认方法

Java8 允许在接口中添加一个或者多个默认方法,在 SingleFncInterface 接口中 print() 就是一个默认方法。增加默认方法是为了给接口添加新方法的同时不影响已有的实现,不需要修改全部实现类。

@FunctionalInterface
interface SingleFncInterface {
    void doSomething(String str);

    default void print() {
        System.out.println("default method.");
    }
}

3. Optional 类

在 Java8 之前,空指针异常是编码时最需要注意的异常,我们往往都需要手动对变量进行 null 值判断,对可能的空指针异常进行捕获处理。Java8 提供的 Optional 类可以以比较优雅的方式进行空值判断,解决空指针异常。

import org.junit.Before;
import org.junit.Test;

import java.util.Optional;

public class OptionalExample {

    private Person person;
    private Car car;
    private Insurance insurance;

    @Before
    public void init() {
        insurance = new Insurance("Tesla");
        car = new Car(Optional.of(insurance));
        person = new Person(Optional.of(car));
    }

    @Test
    public void test1() {
        //允许传递为 null 的参数
        Optional<Insurance> insurance = Optional.ofNullable(this.insurance);
        Optional<String> s = insurance.map(insurance1 -> insurance1.getName());
        System.out.println(s);
    }

    @Test
    public void test2() {
        Optional<Person> person = Optional.of(this.person);
        String name = person.flatMap(Person::getCar)
                .flatMap(Car::getInsurance)//拿到封装的 Optional<Car>
                .map(Insurance::getName)//直接拿到值
                .orElse("ubknow");
        System.out.println(name);
    }

    @Test
    public void test3() {
        Optional<Car> c = Optional.empty();
        Optional<String> s = c.flatMap(Car::getInsurance)
                .map(Insurance::getName);
        System.out.println(s);
        String unknow = s.orElse("unknow");
        System.out.println(unknow);
    }

}

class Person {
    private Optional<Car> car;

    public Person(Optional<Car> car) {
        this.car = car;
    }
    public Optional<Car> getCar() {
        return car;
    }
}

class Car {
    private Optional<Insurance> insurance;

    public Car(Optional<Insurance> insurance) {
        this.insurance = insurance;
    }
    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}

class Insurance {
    private String name;

    public Insurance(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
Optional<Insurance> insurance = Optional.of(this.insurance)
// this.insurance 为 null 返回 Optional.empty
Optional<Insurance> insurance = Optional.ofNullable(this.insurance)

简单来说,如果想得到一个非 null 值的 Optional 使用 Optional.of,允许 null 值的话使用 Optional.ofNullable;

String name = person.flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("unknown");

对于返回一个 Optional 结果集需要使用 flatMap,比如 Person::getCar 方法和 Car::getInsurance,只要单一转换的使用 map,例如 Insurance::getName,如果是 empty 返回 orElse 的内容。

4. Stream 流处理

流 Stream 通过声明的方式来处理数据,可以在管道的节点上对数据进行排序、聚合、筛选、去重和截取等等操作。

import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {

    /**
     * 初始化集合
     */
    List<Fruit> fruits = new ArrayList<>(Arrays.asList(
            new Fruit("apple"),
            new Fruit("banana"),
            new Fruit("orange")
    ));

    /**
     * 遍历
     */
    @Test
    public void outputTest() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);

        integers.forEach(System.out::print);
        System.out.println();
        integers.stream().forEach(System.out::print);
        System.out.println();
        //并行流底层使用Fork/Join框架实现,异步处理,输出不一定是12345
        integers.parallelStream().forEach(System.out::print);
        System.out.println();

    }

    /**
     * 映射
     */
    @Test
    public void mapTest() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        //映射每个元素操作,生成新的结果
        List<Integer> collect = integers.stream().map(n -> n * n).collect(Collectors.toList());
        System.out.println(collect);

        List<String> fruitList = fruits.stream().map(obj -> obj.name="I like ".concat(obj.name)).collect(Collectors.toList());
        System.out.println(fruitList);
    }

    /**
     * 排序、过滤、限制
     */
    @Test
    public void filterTest() {
        List<Integer> integers = Arrays.asList(2, 3, 1, 4, 8, 5, 9, 5);
        List<Integer> collect = integers.stream()
                //.sorted()//排序
                .sorted((x, y) -> y - x)
                .distinct()//去重
                .filter(n -> n < 6)//小于6的数
                .limit(3)//只截取3个元素
                .collect(Collectors.toList());
        System.out.println(collect);
    }

    /**
     * 聚合和统计
     */
    @Test
    public void mergeTest() {
        List<String> strings = Arrays.asList("Hello", " ", "world", "!");
        String collect = strings.stream().collect(Collectors.joining(""));
        System.out.println(collect);

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

}

class Fruit {
    public Fruit(String name) {
        this.name = name;
    }
    String name;

    public void setName(String name) {
        this.name = name;
    }
}

4. Base64 工具

Java 8 内置了 Base64 编码的编码器和解码器,支持三种编解码方式。

import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.util.Base64;

public class Base64Example {
    @Test
    public void test() throws UnsupportedEncodingException {
        //基本
        String s1 = Base64.getEncoder().encodeToString("base64test".getBytes("utf-8"));
        System.out.println(s1);
        System.out.println(new String(Base64.getDecoder().decode(s1), "utf-8"));

        //URL
        String s2 = Base64.getUrlEncoder().encodeToString("base64test".getBytes("utf-8"));
        System.out.println(s2);

        //Mime
        String s3 = Base64.getMimeEncoder().encodeToString("base64test".getBytes("utf-8"));
        System.out.println(s3);
    }
}

5. 新的日期和时间工具

在过去,Java 处理日期和时间我们一般是用 java.util.Datejava.util.Calendar 配合 java.text.SimpleDateFormat 来使用的,缺点是易用性差,线程不安全,不支持时区,新的日期和时间 API 解决了这些问题。

import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class TimeExample {

    @Test
    public void test() {
        LocalTime localTime = LocalTime.now();
        System.out.println(localTime);

        LocalDate localDate = LocalDate.now();
        System.out.println(localDate);

        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(localDateTime);
    }

    @Test
    public void test1() {
        LocalDateTime localDateTime = LocalDateTime.of(2021, 6, 1, 10, 30);
        System.out.println(localDateTime);

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String format = localDateTime.format(formatter);
        System.out.println(format);

        LocalDateTime parse = LocalDateTime.parse("2021-01-01 12:00:00", formatter);
        System.out.println(parse);
    }
}

6. CompletableFuture 异步编程

在 Java8 之前 Future 接口提供了异步执行任务的能力,但对于结果的获取只能通过阻塞或者轮询的方式。为了增强异步编程的功能,Java8 添加了 CompletableFuture 类,CompletableFuture 类实现了 CompletionStage 和 Future 接口,默认使用 ForkJoinPoolmonPool() 线程池。

commonPool 是当前 JVM(进程) 上的所有 CompletableFuture、并行 Stream 共享的,commonPool 的目标场景是非阻塞的 CPU 密集型任务,其线程数默认为 CPU 数量减1,所以对于我们用 java 常做的 IO 密集型任务,默认线程池是远远不够使用的;在双核及以下机器上,默认线程池又会退化为为每个任务创建一个线程,相当于没有线程池。所以使用 CompletableFuture 时要根据业务决定是否需要自定义线程池。

在 CompletableFuture 中带有 Async 的都是异步方法,get 方法是同步的。

@Test
public void futureTest() throws ExecutionException, InterruptedException, TimeoutException {
    //单纯地返回一个值
    CompletableFuture<String> future = CompletableFuture.completedFuture("msg");
    System.out.println(future.get());

    //直接进行运算并返回
    CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2500L);
            //Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1 + 1;
    });
    //是否执行完毕
    System.out.println(supplyAsync.isDone());
    //立刻返回执行结果或异常,否则返回指定值
    System.out.println(supplyAsync.getNow(1));
    //设置超时
    System.out.println(supplyAsync.get(2, TimeUnit.SECONDS));
}

对于多步骤的处理用 thenApply

@Test
public void apply() throws ExecutionException, InterruptedException {
    //多步骤处理,一个步骤处理完把结果返回给下一步继续处理,同步 thenApply,异步 thenApplyAsync
    CompletableFuture<Integer> future = CompletableFuture.completedFuture(1)
            .thenApply(i -> i + 2)
            .thenApplyAsync(i -> i + 3)
            //计算完毕后的处理,不影响 get 返回值
            .whenCompleteAsync((result, exception) -> {
                result *= 10;
                System.out.println("calculate result:" + result);
            });
    System.out.println(future.get());
}

组合方法用 thenCompose

@Test
public void thenComposeExample() throws ExecutionException, InterruptedException {
    String original = "Message";
    //将字符串转换大写,得到结果再转换小写,再组合起来
    CompletableFuture cf = CompletableFuture.completedFuture(original)
            .thenApply(s -> s.toUpperCase())
            .thenCompose(upper -> CompletableFuture.completedFuture(original)
                    .thenApply(s -> s.toLowerCase())
                    .thenApply(s -> upper + s));
    System.out.println(cf.get());
}

等待多个任务一起执行完毕再进行处理可以使用 allOf 方法

@Test
public void allof() throws ExecutionException, InterruptedException {
    List<Integer> integers = List.of(1, 2, 3);
    List<CompletableFuture<Integer>> futureList = integers.stream()
                    .map(item -> CompletableFuture.completedFuture(item).thenApplyAsync(num -> num * num))
                    .collect(Collectors.toList());
    CompletableFuture<Void> allof = CompletableFuture
            .allOf(futureList.toArray(new CompletableFuture[futureList.size()]))
            .whenCompleteAsync((result, exception) -> {
                futureList.forEach(cf -> {
                    System.out.println(cf.getNow(0));
                });
            });
    //handle 住看输出结果,因为是上面都用异步的,这里不等很可能看不到输出
    allof.get();
}

7. 参考

  • [1] Java Platform SE 8
  • [2] Java 8 新特性 | 菜鸟教程
  • [3] CompletableFuture避坑1——需要自定义线程池

原文链接。访问我的个人网站查看更多文章。

更多推荐

从 Java8 到 Java11 再到 Java17 的新特性(1)