likes
comments
collection
share

轻松搞懂java8-17新特性

作者站长头像
站长
· 阅读数 33

.

前言

随着SpringBoot 3.0 将Java 17 设置为最低版本 我们应该明白 Java8的时代总有一天会过去。

而不再是"他发任他发,我用Java8"。

就像Java6是主流的时候,我们认为Java8不会替代6一样,所以随着时间的到来,Java17代替Java8也是必然的!

那么 就让我们来回顾并且学习一下这些新特性吧!!!

Java 8关键特性回顾

Lambda表达式

Lambda表达式是Java编程语言中的一种特殊语法,用于描述一个函数式接口。Lambda表达式由参数列表、箭头符号和一个表达式或语句块组成,其中参数列表指定了传递给Lambda表达式的参数,箭头符号"->"将参数列表和Lambda表达式的主体分开,主体可以是单个表达式或多个语句的语句块。

(parameter1, parameter2, ...) -> { expression }

或者:

(parameter1, parameter2, ...) -> statement

其中,parameter1, parameter2,...是Lambda表达式的参数列表,expression是Lambda表达式的主体,statement是Lambda表达式的主体(语句块)。

例如,下面是一个简单的Lambda表达式,它接收两个整数作为参数,并返回它们之和:

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

Lambda表达式可以在函数式接口的上下文中使用,例如:

interface MyFunctionalInterface {
    int calculate(int x, int y);
}

public class Main {
    public static void main(String[] args) {
        MyFunctionalInterface sum = (x, y) -> x + y;
        System.out.println(sum.calculate(10, 5));  // 输出 15
    }
}

在这个例子中,我们定义了一个函数式接口MyFunctionalInterface,它有一个方法calculate,接收两个int类型的参数并返回一个int类型的值。然后我们使用Lambda表达式创建了一个MyFunctionalInterface类型的实例sum,它将两个参数相加。

请注意,在此示例中,我们可以通过Lambda表达式来为MyFunctionalInterface接口的calculate方法提供实现。由于MyFunctionalInterface是一个函数式接口,因此我们可以使用Lambda表达式来创建它的实例并实现其抽象方法。

关于匿名类创建的简化


例如,如果我们有一个接口:

interface MyInterface {
    void doSomething();
}

在Java 8之前,我们可以这样实现该接口:

MyInterface myInterface = new MyInterface() {
    @Override
    public void doSomething() {
        System.out.println("Doing something");
    }
};

通过使用Lambda表达式,我们可以更简洁地实现同样的功能:

MyInterface myInterface = () -> System.out.println("Doing something");

Lambda表达式的引入大大简化了代码,并使得Java中的函数式编程更加易于使用和理解。

方法引用

Lambda 表达式还支持方法引用(Method Reference)的语法形式,其中 A::a 表示对类 A 的实例方法 a() 的引用。

方法引用可以简化 Lambda 表达式的书写,使代码更加简洁易懂。它可以用于任何可用于函数式接口的 Lambda 表达式上下文中。以下是一些使用方法引用的示例:

// 引用静态方法
Arrays.asList("apple", "banana", "orange").forEach(System.out::println);

// 引用实例方法
List<String> list = Arrays.asList("apple", "banana", "orange");
list.stream().map(String::toUpperCase).forEach(System.out::println);

// 引用构造方法
Supplier<List<String>> supplier = ArrayList::new;
List<String> list = supplier.get();

在第一个示例中,我们使用了 System.out::println 方法引用来打印输出字符串列表中的每个元素。这相当于将以下 Lambda 表达式传递给 forEach() 方法:

s -> System.out.println(s)

在第二个示例中,我们使用了 String::toUpperCase 方法引用来将字符串列表中的每个元素转换为大写字母。这相当于将以下 Lambda 表达式传递给 map() 方法:

s -> s.toUpperCase()

在第三个示例中,我们使用了 ArrayList::new 构造方法引用来创建一个新的 ArrayList 实例。这相当于将以下 Lambda 表达式传递给 get() 方法:

() -> new ArrayList<String>()

注意,方法引用只是一个语法形式,本质上仍然是 Lambda 表达式。因此,它们仍然需要满足函数接口的定义,并且在使用时会被编译器转换为对应的函数式接口实例。

加深练习

  1. 对数组进行排序
// 使用Lambda表达式对整数数组进行排序
int[] numbers = {5, 2, 8, 1, 9};
Arrays.sort(numbers, (a, b) -> a - b);
System.out.println(Arrays.toString(numbers));
  1. 遍历集合并打印元素
// 使用Lambda表达式遍历字符串列表并打印每个元素
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
names.forEach(name -> System.out.println(name));
  1. 在集合中查找满足条件的元素
// 使用Lambda表达式在字符串列表中查找长度大于等于5的第一个元素
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
String foundName = names.stream()
                        .filter(name -> name.length() >= 5)
                        .findFirst()
                        .orElse(null);
System.out.println(foundName);
  1. 将集合中的元素映射成新的集合
// 使用Lambda表达式将字符串列表中的每个元素转换为大写字母,并收集到一个新的列表中
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
List<String> upperCaseNames = names.stream()
                                    .map(name -> name.toUpperCase())
                                    .collect(Collectors.toList());
System.out.println(upperCaseNames);

Optional类

创建方法

of()、empty() 和 ofNullable() 都是用于创建 Optional 对象的方法。

  1. of() 方法:该方法接受一个非空的值,并返回一个包含该值的 Optional 对象。如果传入的值为 null,则会抛出 NullPointerException 异常。示例如下:
String value = "hello";
Optional<String> optional = Optional.of(value); // 创建包含 "hello"Optional 对象
  1. empty() 方法:该方法返回一个空的 Optional 对象,即该对象中没有任何值。可以直接使用该方法创建一个空的 Optional 对象,示例如下:
Optional<String> optional = Optional.empty(); // 创建一个空的 Optional 对象
  1. ofNullable() 方法:该方法接受一个可以为 null 的值,并返回一个包含该值的 Optional 对象。如果传入的值为 null,则返回一个空的 Optional 对象。示例如下:
String value = null;
Optional<String> optional = Optional.ofNullable(value); // 创建一个空的 Optional 对象

需要注意的是,在使用 get() 方法获取 Optional 对象中的值时,如果对象为空,那么会抛出 NoSuchElementException 异常。因此建议在使用 get() 方法之前先调用 isPresent() 方法判断对象是否存在。而如果只是想要获得默认值,可以使用 orElse() 或者 orElseGet() 方法来避免这种异常。

实际运用

Optional 类是 Java 8 中新增的一个类,它可以用于表示一个值可能存在,也可能不存在的情况。在使用Optional 类时,我们可以通过 isPresent() 方法先判断值是否存在,如果存在则可以通过 get() 方法获取该值,否则可以执行一些默认操作或者抛出异常。

Optional<String> optional = Optional.ofNullable("hello");
if (optional.isPresent()) {
    String value = optional.get();
    System.out.println(value);
} else {
    System.out.println("value is null");
}

在这个示例中,我们使用 ofNullable() 方法创建了一个 Optional 对象,并给它赋予了一个非空的值 "hello"。然后使用 isPresent() 方法检查值是否存在,如果存在,则使用 get() 方法获取该值并输出,否则输出 "value is null"。

需要注意的是,当 Optional 对象中的值为 null 时,调用 get() 方法将会抛出 NoSuchElementException 异常。因此,我们可以通过 orElse() 或者 orElseGet() 方法来指定一个默认值,在值为 null 的情况下返回该默认值,示例如下:

Optional<String> optional = Optional.ofNullable(null);
String value = optional.orElse("default");
System.out.println(value);

在这个示例中,我们创建了一个 Optional 对象并给它赋予了一个 null 值。然后通过 orElse() 方法指定了一个默认值 "default",在值为 null 的情况下返回该默认值并输出。

应用场景

Optional 类的主要应用场景是解决空指针异常(NullPointerException)问题。在 Java 开发中,我们经常会遇到需要判断一个值是否为 null 的情况,如果不加以处理,这样的代码很容易出现空指针异常,导致程序崩溃或者运行出错。

通过使用 Optional 类,我们可以清晰地表示某个值可能存在,也可能不存在的情况,从而避免了空指针异常的发生,并让代码更加健壮和可读性更高。

另外,Optional 类还有以下一些应用场景:

  1. 作为方法的返回值类型,表示该方法可能返回一个非空的值,也可能返回一个空值。
  2. 用作方法参数的类型,表示该参数可以接受一个非空的值,也可以接受一个空值。
  3. 用于避免分支语句中的重复代码。

例如,假设我们要获取一个用户的姓名和年龄,并且在获取过程中需要判断两个字段是否为空。在没有使用 Optional 类的情况下,可能会写出以下代码:

public void processUser(User user) {
    if (user == null) {
        throw new IllegalArgumentException("user must not be null");
    }
    String name = user.getName();
    if (name == null || name.isEmpty()) {
        throw new IllegalArgumentException("user name must not be null or empty");
    }
    Integer age = user.getAge();
    if (age == null || age < 0) {
        throw new IllegalArgumentException("user age must not be null or negative");
    }
    // do something with the user information
}

如果使用 Optional 类来处理,可以将上述代码简化为如下形式:

public void processUser(User user) {
    Optional.ofNullable(user).orElseThrow(() -> new IllegalArgumentException("user must not be null"));
    String name = Optional.ofNullable(user.getName()).orElseThrow(() -> new IllegalArgumentException("user name must not be null or empty"));
    Integer age = Optional.ofNullable(user.getAge()).filter(a -> a >= 0).orElseThrow(() -> new IllegalArgumentException("user age must not be null or negative"));
    // do something with the user information
}

通过使用 Optional 类,我们可以将对用户对象和它的属性是否为空的判断和默认操作简化为一行代码,并且可读性更高,减少了出错的概率。

But!!!


在某些情况下,手动对字段进行校验可能更加简单和可读性更高。例如,在代码中只涉及到一个或者两个字段的情况下,使用 Optional 类可能会显得过于繁琐。

然而,在处理复杂的对象或者多个字段的情况下,使用 Optional 类可以使代码更加简洁、可读性更高,并且避免了大量重复的判断代码。此外,使用 Optional 类还有利于编写更加健壮的代码,避免了空指针异常等常见问题。

因此,使用 Optional 类是否更好,需要根据具体的情况来决定。在实际开发中,建议结合具体场景进行选择,以达到最佳的代码效果。

Java 9新特性

模块化开发

即 Java Platform Module System (JPMS),它允许将一个应用程序或库拆分为多个独立的模块,从而更好地管理代码依赖和复杂度。

在模块化开发中,每个模块都有自己的名称、版本以及依赖关系,这些信息都被记录在模块描述文件(module-info.java)中。通过使用模块描述文件,在编译时就可以检查模块之间的依赖关系,并确保所有需要的依赖项都已经加载。

在定义模块时,可以指定哪些包和类是公共的,哪些是私有的。这样可以使得模块之间的接口更加清晰明确,减少了对外暴露的接口数量,提高了代码的可维护性。

此外,Java 9 还引入了 jlink 工具,可以将应用程序打包成一个独立的执行文件,其中包含了应用程序所需的所有依赖项和运行时环境,从而简化了应用程序的部署和分发过程。

总体来说,Java 9 中的模块化系统是一种新的代码组织方式,它能够帮助我们更好地管理代码依赖和复杂度,提高了代码的可维护性和可重用性,同时也使得代码更加清晰明确。

如何使用

首先,我们需要在模块的根目录下创建一个 module-info.java 文件,并定义该模块的名称和依赖关系。例如,我们创建了一个名为模块 mymodule 的模块,它依赖于 Java 的标准库模块 java.base

module mymodule {
    requires java.base;
}

接着,我们可以在该模块中定义一些公共的类和接口,以及提供给其他模块使用的服务。例如,我们在 mymodule 模块中定义了一个名为 MyService 的接口和一个实现该接口的类 MyServiceImpl

package com.example.mymodule;

public interface MyService {
    void sayHello();
}

package com.example.mymodule;

public class MyServiceImpl implements MyService {
    public void sayHello() {
        System.out.println("Hello from MyService!");
    }
}

最后,我们可以在另一个模块中使用 mymodule 模块提供的服务。例如,我们在一个名为 app 的模块中使用 MyService 接口来输出一条消息:

package com.example.app;

import com.example.mymodule.MyService;
import java.util.ServiceLoader;

public class MyApp {
    public static void main(String[] args) {
        ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
        for (MyService service : loader) {
            service.sayHello();
        }
    }
}

在这个示例中,我们使用 ServiceLoader 类来加载 mymodule 模块中的 MyService 接口实现类,并输出一条消息。

需要注意的是,为了能够编译和运行这个示例,我们还需要使用 java 命令的 -p 参数来指定模块路径,以及指定应用程序的主模块。例如,在命令行上执行以下命令:

javac -d out --module-source-path src -m mymodule
javac -d out --module-source-path src -m app
java -p out -m app/com.example.app.MyApp

这样就可以成功地编译和运行该示例了。

四种模块化开发

Java 9 中提供了四种模块类型,分别是:

java.base:这是所有Java模块的基础,包含了Java运行时系统中的基本类和资源文件。

java.se:Java标准版模块,它包含了大多数的Java SE API。

java.xml.bind:Java XML绑定模块,包含了Java体系结构定义语言(XML)和Java对象之间的转换工具。

jdk.management:JDK管理模块,提供了JMX(Java管理扩展)API的实现,用于管理Java应用程序的运行时状态。

除此之外,Java 9 还提供了其他许多模块,例如 java.sql, java.logging 等。每个模块都有自己的名字、版本号和依赖关系,这使得Java应用程序的开发、部署和维护更加可靠和可控。

作用与效果

模块化开发可以提供以下作用和效果:

  1. 更好的可维护性:通过将代码组织成逻辑上相互独立、职责明确的模块,使得代码结构更加清晰,易于理解和维护。
  2. 更好的可重用性:模块可以被其他应用程序或库重复使用,从而避免了重复编写相同功能的代码。
  3. 更好的可扩展性:新增一个功能时,只需要添加对应的模块,而不需要修改应用程序的其余部分。这极大地减少了代码变更对整个应用程序的影响。
  4. 更好的安全性:Java 9 引入了模块系统,可以定义模块之间的依赖关系和访问权限,从而有效地控制应用程序的访问权限,提高安全性。
  5. 更好的性能:由于模块之间明确了依赖关系,JVM 可以进行更精确的优化,提高应用程序的性能。
  6. 更好的易用性:模块系统可以自动管理依赖关系,提供了一种更简单的方式来处理类路径问题,使得应用程序的部署更加容易。

总之,模块化开发是一种更加规范、可控的方式来组织代码,它可以提高代码的可维护性、可重用性、可扩展性和安全性,同时也可以提高应用程序的性能和易用性。

JShell交互式编程

Java 9 中引入了 JShell,它是一个交互式的 Java 编程工具。使用 JShell,可以在不用创建类或方法的情况下,直接输入和执行 Java 代码,同时还提供了自动补全、错误提示等功能,使得编写和测试 Java 代码更加简单快捷。

JShell 可以通过命令行或者 IDE 插件的方式进行使用。以下是一些常用的 JShell 命令

/help:显示所有可用的 JShell 命令。

/vars:显示当前所有已定义的变量。

/methods:显示当前所有已定义的方法。

/imports:显示当前所有已导入的包和类。

/edit:编辑指定行号的代码。

/save:将当前 JShell 会话保存到文件中。

/reset:重置 JShell,并清除所有已定义的变量和方法。

以下是一个简单的示例:

以下是JShell 的应用场景

  1. 快速原型开发:使用 JShell 可以快速地尝试各种 Java 代码片段,从而更快地原型开发和调试。
  2. 学习和教学:JShell 是一个交互式的环境,可以帮助初学者更好地理解和学习 Java 编程语言。
  3. 调试和测试:使用 JShell 可以方便地进行代码片段的调试和测试,从而更快地定位和解决代码问题。
  4. API 探索:开发人员可以使用 JShell 来探索各种 Java API 和库的使用方式。
  5. 快速验证想法:JShell 可以帮助开发人员快速验证其想法,并且可以在大量重复的代码之间节省时间。

总之,JShell 可以帮助开发人员更高效地编写和测试 Java 代码,从而加速开发速度,提高开发效率。

private()方法

在方法声明前加上 private 关键字来创建私有方法。这意味着只有在同一类中的其他方法才能够访问该方法,而无法从另一个类中进行访问。

下面是一个示例:

public class MyClass {
    public void publicMethod() {
        // 可以调用 privateMethod
        privateMethod();
    }
    
    private void privateMethod() {
        // 只能被 MyClass 中的其他方法调用
    }
}

在上面的代码中,privateMethod 方法只能被 MyClass 类中的其他方法调用,而不能从其他类中直接访问它。

希望这能够帮助你了解如何在Java 9中创建私有方法。

工厂方法

集合类新增了一些工厂方法来创建不可变的集合对象。这些方法返回的集合对象是只读的,并且具有更好的性能和更小的内存占用。

这些工厂方法在实际业务处理中具有以下几个作用:

  1. 简化代码:使用集合工厂方法可以让代码更加简洁明了。通过这些方法创建集合实例不需要手动遍历和添加元素,而是直接将元素以参数形式传递给方法,从而使代码更加简洁。
  2. 安全性:由于集合工厂方法返回的集合实例是不可变的,因此它们比手动创建的集合实例更加安全。这些集合实例不能被修改或被其他线程篡改,因此能够保证数据的一致性。
  3. 性能:在创建小型集合时,使用集合工厂方法通常比手动创建集合实例更快。这是因为这些方法利用了内部优化技术,能够更快地创建出集合实例。
  4. 可读性:使用集合工厂方法可以使代码更加可读性强,当阅读代码时,我们很容易看出这里创建了一个不可变的集合实例,而不需要分析复杂的代码来了解集合如何初始化。

总之,Java 9 中的集合工厂方法可以帮助我们更加轻松、简洁地创建集合实例,提高了代码的可读性和安全性,并且在处理小型集合时具有更好的性能。

// 创建一个不可变的 List 实例,其中包含 "apple", "banana", "orange" 三个元素
List<String> fruits = List.of("apple", "banana", "orange");

// 创建一个不可变的 Set 实例,其中包含 "red", "green", "blue" 三个元素
Set<String> colors = Set.of("red", "green", "blue");

// 创建一个不可变的 Map 实例,其中包含两个键值对 ("key1", "value1"), ("key2", "value2")
Map<String, String> map = Map.of("key1", "value1", "key2", "value2");

改进的Stream() API

Java 9 在 Stream API 中引入了一些改进,包括:

ofNullable() 方法:这个方法允许我们创建一个可能为空的 Stream 对象,避免使用 null 值时出现空指针异常。

takeWhile() 和 dropWhile() 方法:这两个方法分别返回符合和不符合指定条件的元素,遇到第一个不符合条件的元素后就停止执行流操作。

iterate() 方法的重载版本:新增了一个可以设置流大小上限的 iterate() 方法,避免了无限制生成流的问题。

ofNullable() 和 iterate() 方法的优化:Java 9 对这两个方法进行了优化,提高了它们的性能。

toArray() 方法的重载版本:toArray() 方法现在可以接受一个 IntFunction 参数,用于创建指定类型和长度的新数组。

Optional 类的 stream() 方法:Optional 类现在也支持通过 stream() 方法创建一个 Stream 对象,这样可以方便地将 Optional 对象转换为 Stream 对象进行操作。

这些改进使得 Stream API 更加易于使用和灵活,同时提高了性能和效率。

Java 10新特性

var局部变量类型推断

Java 10 引入了局部变量类型推断功能,也称为 "var" 类型。这项新功能允许开发人员在不显式指定变量类型的情况下声明局部变量,而是使用关键字 "var" 来代替。

例如,使用 var 定义一个字符串变量:

var message = "Hello, World!";

编译器会自动推断出变量类型为 String,并生成相应的字节码。可以通过使用 getClass() 方法来验证变量类型:

System.out.println(message.getClass()); // 输出:class java.lang.String

注意,var 只能用于局部变量的定义,不能用于实例变量、静态变量、方法参数或返回值等。同时,还需要注意避免过度使用 var,保持代码的可读性和清晰性。

总之,局部变量类型推断使得 Java 代码更加简洁、易读和安全,能够提高开发效率和减少出错概率。

Java 11新特性

用于lambda的形参局部变量

在 Java 11 中,可以在 lambda 表达式的形参列表中使用 var 关键字来声明局部变量,从而可以在 lambda 表达式中使用这些变量。举个例子,假设有一个 List 类型的集合,想要过滤掉长度大于等于 5 的字符串并输出剩余的字符串,可以使用以下代码:

List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
list.stream()
    .filter(s -> {
        var length = s.length();
        return length < 5;
    })
    .forEach(System.out::println);

在这个例子中,使用 var 关键字声明了一个名为 length 的局部变量,并将其赋值为字符串 s 的长度。然后通过 filter() 方法过滤出长度小于 5 的字符串,并通过 forEach() 方法输出剩余的字符串。

需要注意的是,var 关键字只能用于局部变量的声明,不能用于成员变量、方法参数和返回值类型的声明。此外,在使用 var 声明局部变量时,必须进行初始化,因为编译器需要推断出变量的类型。如果不进行初始化,则会导致编译错误。

针对String类的方法增强

Java 11 对 String 类进行了一些增强,包括但不限于:

isBlank() 方法:用于检查一个字符串是否为空或仅由空格字符组成,返回值为 boolean 类型。

lines() 方法:用于将一个字符串按行分割成多个子串并返回一个 Stream 对象。

repeat(int count) 方法:用于重复一个字符串 count 次并返回新的字符串。

strip() 和 stripLeading()、stripTrailing() 方法:用于去除字符串两端的空格(包括 Unicode 中的空格字符),分别返回去除左侧空格、右侧空格和两侧空格后的新字符串。

这些增强使得 String 类具有更好的易用性和灵活性,可以更方便地处理字符串相关的业务逻辑。

全新的HttpClient API

以下是一个使用 Java 11 HttpClient API 发送 GET 请求并解析响应的示例代码:

public class HttpClientDemo {
    public static void main(String[] args) throws Exception {
        // 创建 HttpClient 对象
        HttpClient client = HttpClient.newHttpClient();

        // 创建请求对象
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://www.baidu.com/"))
                .build();

        // 发送请求并获取响应
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        // 输出响应结果
        System.out.println(response.body());
    }
}

在这段代码中,我们首先创建了一个 HttpClient 对象,然后使用它来发送一个 GET 请求,请求的地址为 www.baidu.com/。我们使用 HttpRequest 类来构建请求对象,并调用 HttpClient 的 send() 方法来发送请求并获取响应。最后,我们从响应中获取状态码和响应体,并将它们输出到控制台上。

值得注意的是,这里我们使用了 HttpResponse.BodyHandlers.ofString() 来指定响应体的处理方式为字符串类型。还有其他一些处理方式可供选择,例如将响应体处理为字节数组、文件、流等。另外,在实际开发中,我们还可以对请求进行更多的配置,例如设置请求头、请求体、代理等。

Java12-16新特性

Switch表达式

Java 12 中引入了新的 switch 表达式语法,可以更加简洁和灵活地编写 switch 语句。Java 12 到 Java 16 版本都对 switch 表达式做出了一些增强和改进。

在使用 switch 表达式时,可以省略掉常规的 break 和 case 关键字,并且可以将多个 case 分支合并成一个,如下所示:

int num = 2;
String result = switch (num) {
    case 1 -> "One";
    case 2, 3 -> "Two or Three";
    case 4 -> "Four";
    default -> "None";
};

System.out.println(result);

这段代码中,我们使用新的 switch 表达式语法对变量 num 进行判断,并根据不同的情况返回不同的字符串结果。可以看到,我们使用箭头符号 -> 来连接 case 分支和相应的返回值,同时可以将多个 case 分支合并成一个,并使用逗号来分隔多个条件。最终,将表达式的结果赋值给变量 result。

除此之外,Java 13、Java 14 和 Java 15 都为 switch 表达式做出了一些增强,例如可以添加 yield 语句、使用 lambda 表达式等。需要注意的是,在使用 switch 表达式时,要确保所有的分支都有返回值,并且返回值类型必须一致。

文本块

Java 12 到 16 之间增加了文本块(Text Blocks)这一新特性,用于更方便地编写多行字符串。

在使用文本块时,可以使用三个双引号 """ 来表示一个多行的字符串。例如:

String html = """
              <html>
                  <body>
                      <p>Hello, world!</p>
                  </body>
              </html>
              """;

使用文本块时,不需要添加额外的换行符或转义字符,因为所有的空白符(包括换行符)都会保留在生成的字符串中。这使得多行字符串的编写变得更加直观和易读。

此外,还可以使用嵌入式表达式(Embedded Expressions)来将变量或表达式插入到文本块中。嵌入式表达式由一个美元符号和花括号组成,如下所示:

String name = "John";
int age = 30;
String message = """
                 Name: ${name}
                 Age: ${age}
                 """;

在这个例子中,name 和 age 变量的值将会被嵌入到 message 字符串中。

这些文本块的新特性使得 Java 中多行字符串的创建和处理更加容易和优雅。

轻松搞懂java8-17新特性

新的instanceof语句

Java 14 引入了一种新的 instanceof 语法,称为“模式匹配 instanceof”,它可以使代码更加简洁、易读和类型安全。

在传统的 instanceof 中,我们需要将对象强制转换为特定的类型,并且使用该类型来执行操作。示例如下:

if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.toUpperCase());
}

在 Java 14 中,可以使用“模式变量”(Pattern Variable)来完成此操作,语法为 instanceof 后跟一个类型和一个变量名,如下所示:

if (obj instanceof String str) {
    System.out.println(str.toUpperCase());
}

在这个例子中,如果 obj 是 String 类型的实例,就会将其转换为 str 变量,然后使用它执行操作。通过这种方式,可以避免使用不必要的类型转换,更加直观地表达意图,并增加类型安全性。

此外,在 Java 16 中,对模式匹配 instanceof 进行了一些改进,使其更加灵活和易用。例如,在 switch 表达式中也可以使用模式匹配 instanceof,如下所示:

Object obj = "hello";

String result = switch(obj) {
    case String str && str.length() > 5 -> "long string";
    case String str -> "short string";
    case Integer i -> "integer";
    default -> "unknown";
};

System.out.println(result); // 输出:short string

在这个例子中,我们使用模式变量 str 和 && 运算符来对字符串进行特定的匹配。这使得 switch 表达式更加灵活和强大,可以处理更复杂的数据类型和条件。

空指针异常改进

在 Java 14 中,引入了对空指针异常的改进,称为“可空性注解”(Nullability Annotations)。该特性允许开发人员在代码中使用注解来指示哪些变量、参数或返回值可以为空,哪些不可以为空。这有助于编译器更好地检测潜在的空指针异常,并在编译时提供更好的错误信息。

具体地说,Java 14 引入了两个新的可空性注解:@Nullable 和 @NotNull。@Nullable 注解表示被注解的元素可以为 null,而 @NotNull 则表示被注解的元素不应该为 null。

例如,假设我们要定义一个方法来接收一个字符串参数,并且希望确保该参数不为 null。我们可以在方法签名上使用 @NotNull 注解,如下所示:

public void processMessage(@NotNull String message) {
    // ...
}

在这个例子中,如果调用者尝试将 null 值传递给该方法,编译器将会提出警告。

除了 @Nullable 和 @NotNull 注解之外,还有一些其他的注解可以用来指定可空性,例如 @NonNullApi 和 @NonNullFields。这些注解可以用于整个包或类,以指示它们的成员或字段是否应该默认为非空或可空。

这些可空性注解可以使代码更加清晰、易读和类型安全,特别是在处理大型代码库或与其他团队成员合作时。然而,需要注意的是,这些注解并不会自动防止空指针异常,开发人员仍然需要谨慎地编写代码以避免这种情况的发生。

记录类型

Java 12-16 的记录类型是一项新特性,它为Java引入了一种方便的方式来声明不可变的数据类。以下是该特性的一些要点:

  1. 记录类型使用关键字“record”进行声明,可以看作是一种轻量级的类定义方式。
  2. 记录类型的属性可以通过构造函数或者自动生成的getter方法进行访问,这些方法会自动根据属性名称生成。
  3. 记录类型中的属性默认是final和private的,并且不能被修改,因此记录类型是不可变的。
  4. 记录类型还支持equals()、hashCode()和toString()等标准方法的自动生成,这样可以更方便地进行对象比较和打印。
  5. 记录类型可以继承其他类或接口,并且也可以实现接口,从而具备更多的灵活性。
  6. 记录类型提供了一个方便的方式来快速创建对象,可以直接使用表达式进行初始化。
  7. 记录类型还支持局部声明,可以将其嵌套在其他类或方法内部��从而更加灵活地利用该特性。

总之,记录类型是一项非常有用的新特性,它可以使Java代码更加简洁、易于理解和维护。

使用

我们可以创建一个简单的Person类,用于存储人员信息:

public record Person(String name, int age) {}

这里使用了关键字record声明了一个名为Person的记录类型,并定义了两个属性name和age。由于使用了关键字record,所以这个类是不可变的,也就是说,这两个属性不能在其他地方修改。

这个记录类型会自动生成以下内容:

构造函数,用于初始化对象。

自动实现了equals()、hashCode()和toString()方法,方便进行对象比较和打印。

自动实现了getter方法,用于获取属性值。

我们可以创建一个Person对象并输出其信息:

Person person = new Person("Tom", 25);
System.out.println(person.name());
System.out.println(person.age());
System.out.println(person);

将会输出以下结果:

Tom
25
Person[name=Tom, age=25]

可以看到,记录类型确实很简洁方便,可以让代码更加易于理解和维护。

java 17新特性

密封类型

Java 17 提供了一项新特性——密封类型。这是一种限制类继承的机制,它允许程序员定义一个密封类,该类只能被一组已知的子类继承。这个特性可以让程序员更加严格地控制类型继承,从而使代码更加安全、健壮和易于维护。

以下是对Java 17密封类型的一些要点:

  1. 密封类使用关键字sealed进行声明,可以看作是一种特殊的抽象类定义方式。
  2. 密封类可以显式声明允许继承的子类,这些子类需要使用关键字permits进行声明,例如: sealed class Shape permits Circle, Rectangle, Triangle {}
  3. 密封类的直接子类必须是预定义的子类或者在密封类中明确声明的子类,否则编译器将报错。
  4. 密封类的构造函数默认是私有的,并且不能被外部调用,因此密封类是不可实例化的。
  5. 密封类可以定义抽象方法,也可以重写父类的非抽象方法。
  6. 密封类可以与switch表达式一起使用,以实现更加清晰和安全的代码逻辑。

总之,密封类型是一项非常有用的新特性,它可以让程序员在类型继承方面拥有更多的控制权,并使代码更加稳定和易于维护。

下面是一个示例,演示了如何使用Java 17中的密封类型:

sealed interface Shape permits Circle, Rectangle, Triangle {
    // Shape接口,声明了允许的子类为Circle、Rectangle和Triangle
    double area();
}

final class Circle implements Shape {
    // Circle实现了Shape接口
    private final double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    public double area() {
        return Math.PI * radius * radius;  // 计算圆的面积
    }
}

final class Rectangle implements Shape {
    // Rectangle实现了Shape接口
    private final double width;
    private final double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    public double area() {
        return width * height;  // 计算矩形的面积
    }
}

final class Triangle implements Shape {
    // Triangle实现了Shape接口
    private final double base;
    private final double height;
    
    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }
    
    public double area() {
        return 0.5 * base * height;  // 计算三角形的面积
    }
}

可以看到,这个示例使用了密封类型来限制Shape接口的可继承子类,并分别定义了三个子类Circle、Rectangle和Triangle,它们都实现了Shape接口,并提供了特定的area()方法用于计算各自的面积。

转载自:https://juejin.cn/post/7225092351357435960
评论
请登录