从 JDK8 之后,Java 的更新策略改为以时间驱动的方式,每六个月发布一个新的Java版本,每三年发表一个长期支持版本。一般如果要对旧 JDK 进行升级,都会选择长期支持版,JDK11 和最近更新的 JDK17 是长期支持版本。但是由于商业项目更看重稳定性,更新 JDK 带来的收益不大,大多数人不愿意踩坑去更新 JDK。因此,很多人都只是从新闻了解到新 JDK 的新特性,平常开发没有接触到,甚至有些在用 JDK8 的人连 JDK8 的新特性都用不利索或者直接就不知道。其实许多新特性是可以简化我们的开发,能让我们以更优雅的方式实现功能。接下来我将分三篇文章分别简单介绍 JDK8、JDK9-JDk11 和 JDK12-JDK17 在编码方面的一些新功能,至于虚拟机的改进和其他部门这里暂不做讨论。
先从 Java8 开始说起,看看部分常用的新特性。
本文源码地址:code-note
1. Lambda 表达式
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),让匿名内部类的写法更简便。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class LambdaTest {
public LambdaTest() { } public LambdaTest(String str) { }
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
注解标记,限制只允许定义一个抽象方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Test public void singleFunTest() { LambdaTest.interfaceTest((String str) -> { System.out.println("single function interface. param:" + str); });
SingleFncInterface s = str -> System.out.println(str); s.doSomething("123");
LambdaTest.interfaceTest(System.out::println); }
|
可以看出 lambda 表达式的语法格式是如:(parameters) -> expression/statements,特殊的还有更加简化的方法引用方式。
方法引用可分为三种,静态、实例和构造引用,使用例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Test public void refTest() { 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; }
|
根据输入和返回参数的不同,JDK 中提供了四种类型的函数式接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
@Test public void funTest() {
Function<Integer, Integer> function = n -> n*n; Integer apply = function.apply(10); System.out.println(apply);
Consumer<String> consumer = System.out::println; consumer.accept("output msg.");
Supplier<Integer> supplier = () -> 10*10; Integer integer = supplier.get(); System.out.println(integer);
Predicate<Integer> predicate = num -> num>10; boolean test = predicate.test(20); System.out.println(test); }
|
2. 接口默认方法
Java8 允许在接口中添加一个或者多个默认方法,在 SingleFncInterface
接口中 print()
就是一个默认方法。增加默认方法是为了给接口添加新方法的同时不影响已有的实现,不需要修改全部实现类。
1 2 3 4 5 6 7 8
| @FunctionalInterface interface SingleFncInterface { void doSomething(String str);
default void print() { System.out.println("default method."); } }
|
3. Optional 类
在 Java8 之前,空指针异常是编码时最需要注意的异常,我们往往都需要手动对变量进行 null 值判断,对可能的空指针异常进行捕获处理。Java8 提供的 Optional 类可以以比较优雅的方式进行空值判断,解决空指针异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| 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() { 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) .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; } }
|
1 2 3
| Optional<Insurance> insurance = Optional.of(this.insurance)
Optional<Insurance> insurance = Optional.ofNullable(this.insurance)
|
简单来说,如果想得到一个非 null 值的 Optional 使用 Optional.of
,允许 null 值的话使用 Optional.ofNullable
;
1 2 3 4
| 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 通过声明的方式来处理数据,可以在管道的节点上对数据进行排序、聚合、筛选、去重和截取等等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| 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(); 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((x, y) -> y - x) .distinct() .filter(n -> n < 6) .limit(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 编码的编码器和解码器,支持三种编解码方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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"));
String s2 = Base64.getUrlEncoder().encodeToString("base64test".getBytes("utf-8")); System.out.println(s2);
String s3 = Base64.getMimeEncoder().encodeToString("base64test".getBytes("utf-8")); System.out.println(s3); } }
|
5. 新的日期和时间工具
在过去,Java 处理日期和时间我们一般是用 java.util.Date
、java.util.Calendar
配合 java.text.SimpleDateFormat
来使用的,缺点是易用性差,线程不安全,不支持时区,新的日期和时间 API 解决了这些问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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 接口,默认使用 ForkJoinPool.commonPool() 线程池。
commonPool 是当前 JVM(进程) 上的所有 CompletableFuture、并行 Stream 共享的,commonPool 的目标场景是非阻塞的 CPU 密集型任务,其线程数默认为 CPU 数量减1,所以对于我们用 java 常做的 IO 密集型任务,默认线程池是远远不够使用的;在双核及以下机器上,默认线程池又会退化为为每个任务创建一个线程,相当于没有线程池。所以使用 CompletableFuture 时要根据业务决定是否需要自定义线程池。
在 CompletableFuture 中带有 Async 的都是异步方法,get 方法是同步的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @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); } 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
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void apply() throws ExecutionException, InterruptedException { CompletableFuture<Integer> future = CompletableFuture.completedFuture(1) .thenApply(i -> i + 2) .thenApplyAsync(i -> i + 3) .whenCompleteAsync((result, exception) -> { result *= 10; System.out.println("calculate result:" + result); }); System.out.println(future.get()); }
|
组合方法用 thenCompose
1 2 3 4 5 6 7 8 9 10 11
| @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 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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)); }); }); allof.get(); }
|
7. 参考