java 新特性

179 阅读9分钟

Java8 新特性

1. 接口新特性

Java 接口存在的问题是当接口修改时,实现类也必须修改,为了解决接口的修改与现有实现类不兼容的问题,接口中的方法可以用 default 或者 static 修饰。

default 修饰的方法属于实例方法,有自己的方法体,它可以用 this 调用,可以被子类继承、重写,当类同时实现具有签名相同的default方法的两个不同接口时,这个方法必须要被重写。

static 修饰的方法,它也有自己的方法体,它的使用和类的静态方法一样,它不能被子类继承、重写,只能使用 interfaceName.methodName() 的方式进行调用。

1.1 Java8 中的接口和抽象类有什么区别?

主要区别是:

  • 接口可以多个实现,抽象类只能单继承;
  • 接口的方法是 public abstract 修饰的,变量是 public static final 修饰的。抽象类 可以使用其他修饰符。

接口新增 defaultstatic 关键字是为了解决接口修改与现有的实现不兼容的问题,不是为了替代抽象类,因此接口和抽象类在使用上还是不变。

2. 函数式接口

函数式接口也称为单抽象方法接口,也就是函数式接口有且只有一个抽象方法,但是可以有多个非抽象方法。

函数式接口可以没有 @FunctionalInterface 注解, @FunctionalInterface 注解只是在编译期起到强制规范定义的作用。

3. Lambda 表达式

Lambda 表达式可以使代码变得更加简洁紧凑,让 Java 能支持简单的函数式编程。 Lambda 表达式是一个匿名函数,Java8 允许把函数作为参数传递进方法中。

Lambda 带来的便利有:

  • 代替匿名内部类:这些可以代替的匿名内部类要求必须是函数式接口,也就是说只有一个抽象方法需要被重写。
  • 集合迭代:对于集合可以使用 Lambda 表达式进行迭代。
  • 方法引用:可以使用 ::关键字来传递方法或者构造函数的引用。类似 Person p = Person::new 的写法,本质是将构造函数的引用传递给了方法。

4. Stream

Stream 类似之前的流,它不存储数据,它可以检索和逻辑处理集合数据,包括:筛选、排序、统计、计数等。它的数据源可以是 CollectionArray 等。

Stream 分为串行流,和 ParallelStream 并行流。

Stream 的延迟执行,在执行返回 Stream 类型的方法时,不会立即执行,而会等一个返回非 Stream 类型的方法后才会执行。 下面的方法会先打印:"execute count",然才会循环打印 "filter:" + e 的内容。

public static void main(String[] args) {
    List<Integer> list = List.of(1,2);
    Stream<Integer> filter = list.stream().filter(e -> {
        System.out.println("filter:" + e);
        return true;
    });
    System.out.println("execute count");
    long count = filter.count();
}

5. Optional

Optional 主要用来处理判断空和 null 的问题。 比如:Optional.ofNullable(zoo).map(z -> z.getDog()).map(d -> d.getAge()).ifPresent(age -> System.out.println(age)); 我们无需判断 zoo、z、d 变量是否为空或者 null。

我们可以使用 Optional.empty() Optional.of(T value)Optional.ofNullable(T value)等方法创建 Optional 对象。

Optional 的 flatMap() 函数可以把 map() 函数的结果展开。即对于 List<List<String>> 通过 map() 变化后,得到的是 List<List<String>>,通过 flatMap() 变换后,可以得到 List<String>

日期-时间(Date-Time) api

新的 Date-Time api 解决了 Date 类的大部分痛点:

  • 非线程安全
  • 时区处理麻烦
  • 各种格式化、时间计算繁琐
  • 设计缺陷:Date 类同时包含日期和时间,并且还有一个 java.sql.Date 类,容易混淆。

java.time 的主要类: LocalDateTime: 日期 + 时间,格式为:yyyy-MM-ddTHH:mm:ss.SSS LocalDate: 日期,格式为:yyyy-MM-dd LocalTime: 时间,格式为:HH:mm:ss

对于上面三个类的格式化: LocalDateLocalTimetoString() 方法提供了默认的格式 yyyy-MM-dd 和 HH:mm:ss。

LocalDateTime 可以使用以下方法进行格式化:

LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateTimeStr = dateTime.format(formatter);

字符串/数字 转日期格式: LocalDateTimeLocalDateLocalTime 可以使用对应类的 of 静态方法将数字转为日期。也可以使用对应的 parse 方法将符合格式的字符串转为对应的类型,例子:LocalDate.of(2021, 1, 26)LocalDateTime.parse("2021-01-26 12:12:22")

jdbc 的 时间-日期 与 Java8 的 日期-时间 对应: Date ---> LocalDate Time ---> LocalTime Timestamp ---> LocalDateTime 而之前统统对应 Date,也只有 Date。

时区问题: Java8 引入了 ZonedDateTime 来表示带有时区的时间。

//当前时区时间
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println("当前时区时间: " + zonedDateTime);

//东京时间
ZoneId zoneId = ZoneId.of(ZoneId.SHORT_IDS.get("JST"));
ZonedDateTime tokyoTime = zonedDateTime.withZoneSameInstant(zoneId);
System.out.println("东京时间: " + tokyoTime);

// ZonedDateTime 转 LocalDateTime
LocalDateTime localDateTime = tokyoTime.toLocalDateTime();
System.out.println("东京时间转当地时间: " + localDateTime);

//LocalDateTime 转 ZonedDateTime
ZonedDateTime localZoned = localDateTime.atZone(ZoneId.systemDefault());
System.out.println("本地时区时间: " + localZoned);

//打印结果
//        当前时区时间: 2021-01-27T14:43:58.735+08:00[Asia/Shanghai]
//        东京时间: 2021-01-27T15:43:58.735+09:00[Asia/Tokyo]
//        东京时间转当地时间: 2021-01-27T15:43:58.735
//        当地时区时间: 2021-01-27T15:53:35.618+08:00[Asia/Shanghai]

Java9 新特性

1. JShell

提供了类似 Python 的实时命令行交互工具。

JShell 的一些特点:

  • 它可以处理简单的小逻辑、验证简单的小问题,它比 IDE 更有效率。
  • 代码语句输入完成后,JShell 可以立即返回执行结果,而不再需要编辑器、编译器、解释器。
  • 支持重复变量声明,后面声明的变量会覆盖前面声明的变量。
  • 支持独立的表达式计算,比如: 1 + 1。

2. 模块化系统

一个模块可以看作是:一组唯一命名的、可重用的包、资源和模块描述文件(module-info.java)

任意一个 jar 文件,加上一个模块描述文件,就可以升级为一个模块。

引入模块系统之后, jdk 被重新组织成了 94 个模块。 Java 应用可以通过新增的 jlink 工具创建出只包含所依赖的 jdk 模块来自定义运行时镜像,从而极大的减少 Java 运行时环境的大小。

module my.module {
    //exports 公开指定包的所有公共成员
    exports com.my.package.name;
}

module my.module {
     //exports…to 限制访问的成员范围
    export com.my.package.name to com.specific.package;
}

其他特性:

  • G1 称为默认的垃圾收集器。
  • 提供了快速创建不可变集合的静态工厂方法:List.of()Set.of()Map.ofEntries()
  • String 存储结构优化,由 char[] 变成了 byte[]
  • 接口中允许使用私有方法。
  • try-with-resources 增强,可以在 try-with-resources 语句中使用 effectively-final 变量(初始化后没有更改的变量),例如:var w = new PrintWriter("test.txt"); try (w) {}
  • 提供进程相关的 api 可以获取当前正在运行的 jvm 的进程。

String 存储结构优化有哪些好处?

  • 节约内存空间,由 char[] 变成了 byte[] 之后,对于 ASCII 字符可以节约一半的内存,因为 ASCII 字符只用一个 byte 但是 char 是 2 个 byte。
  • 字符串编码更加灵活,之前用 char 数组存的时候,需要将每个字符编码成 utf-16 ,这对于使用不同字符集的语言来说不太灵活,byte 数组可以支持更多的字符编码格式。
  • 提高字符串操作效率,在字符串拷贝、序列化等场景下,可以避免从 char 数组转换为 byte 数组,从而提高运行效率。

Java10 新特性

1. 提供了局部变量类型推断

提供了 var 关键字,对于类型明确的局部变量可以定义 var 类型。 但是 var 关键字不能用于 null 值、Lambda 表达式、数组类型。

var count=null; //编译不通过,不能声明为 null
var r = () -> Math.random();//编译不通过,不能声明为 Lambda表达式
var array = {1,2,3};//编译不通过,不能声明数组

其他特性:

  • 集合增强:对于 List 、 Set 、 Map 提供了 copyOf() 对于给定的集合,返回一个不可变的拷贝。
  • Collectors 中提供了将流转换为不可变集合的静态方法,例如:list.stream().collect(Collectors.toUnmodifiableList());

Java11 新特性

1. 对 HTTP Client 进行标准化

var request = HttpRequest.newBuilder()
        .uri(URI.create("https://www.baidu.com"))
        .GET()
        .build();
var client = HttpClient.newHttpClient();

// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(e -> {
            // 响应结果
            System.out.println(e);
        });

Java12-13 新特性

String 字符串增强

  • String 字符可以通过 indent() 方法进行缩进。例如:"Java".indent(4),会在字符串 Java 前面添加四个空格,如果是 -4 ,就是减少 4 个空格。
  • String 字符可以通过 transform() 进行拼接。例如:var str = "world".transform(e -> "hello " + e),str = "hello world"

Files 增强

Files 类添加了 mismatch(Path path, Path path2) 方法来对比两个文件。方法返回第一个不匹配的字符的位置。如果文件相同,返回 -1L。

Java14-15 新特性

空指针异常精准提示

使用 jvm 参数 -XX:+ShowCodeDetailsInExceptionMessages 开启更为精准的空指针异常调用信息。

switch 增强

switch 语法引入了类似 Lambda 语法,同时引入了 yield 关键字用来返回值,这样 switch 块的返回值就能赋值给变量了。

 String result = switch (test) {
            case null -> "null";
            case "a", "b", "c" -> "a";
            case "d", "e" -> "b";
            default -> {
                if (test.isEmpty()) {
                    yield "c";
                } else {
                    yield "d";
                }
            }
        };

文本块

对于大量文本,支持文本块特性,同时引入了两个转义字符,\:表示行尾不换行;\s:表示单个空格。

String str = """
                第一行与第二行不换行。 \
                \s但是前面加了一个空格
                """;

Java16 新特性

instanceof 模式匹配增强

在之前使用 instanceof 进行模式匹配后,还需进行强制类型转换。增强后的模式匹配成功后,可以直接当做新的类型使用。

if (o instanceof String s) {
    // 此时 s 已经被强转为字符串类型了。
}

数据类关键字 record

record 关键字用来简化数据类的定义。用 record 声明了数据类之后,就自动获得了它的属性访问方法、以及 toString()hashCode()equals() 方法。

它类似 lombok 插件,并同时使用了 @Getter, @ToString, @EqualsAndHashCode 注解。

它的成员变量都是 final 类型的,它的构造函数就是声明时的函数。

public static void main(String[] args) {
    //使用这个数据类,并访问成员变量
    Rectangle rectangle = new Rectangle(1, 2);
    int length = rectangle.length();
    String str = rectangle.toString();
}

// 用 record 声明一个数据类 
record Rectangle(int length, int width) { }