Java 8 到 Java 22 新特性详解

Java 8 到 Java 22 新特性详解

Java自发布以来一直在不断演进,添加新特性以提升开发效率和性能。本文将介绍Java 8到Java 22的主要新特性,帮助开发者了解各版本的新功能和改进。

Java 8 (2014)

1. Lambda 表达式

Lambda 表达式允许使用简洁的语法定义匿名函数,主要用于简化集合操作和并行处理。

为什么需要 Lambda 表达式?

在 Java 8 之前,匿名内部类通常用于实现功能性接口,例如 RunnableComparator。但这种方式语法冗长且可读性差。Lambda 表达式提供了一种更简洁的方式来实现这些接口,使代码更简洁、可读。

如何定义 Lambda 表达式

Lambda 表达式的基本语法如下:

(parameters) -> expression
或
(parameters) -> { statements; }
示例

下面是一个使用 Lambda 表达式对列表进行排序的示例:

List<String> names = Arrays.asList("张三", "李四", "王五");
names.sort((String a, String b) -> b.compareTo(a));
names.forEach(name -> System.out.println(name));

2. Stream API

Stream API 提供了一种声明性方式处理集合数据,通过链式操作实现复杂的数据处理逻辑。

为什么需要 Stream API?

传统的集合处理方式通常使用迭代器或循环,这种方式代码冗长且易出错。Stream API 提供了一种更简洁和强大的方式来处理集合数据,使得代码更具表达力和可读性。

如何使用 Stream API

Stream API 主要由源、零或多个中间操作和终端操作组成。中间操作是惰性的,只有在终端操作调用时才会执行。

示例

下面是一个使用 Stream API 过滤和收集数据的示例:

List<String> names = Arrays.asList("张三", "李四", "王五");
List<String> filteredNames = names.stream()
                                  .filter(name -> name.startsWith("张"))
                                  .collect(Collectors.toList());
filteredNames.forEach(System.out::println);

3. 默认方法

默认方法(Default Methods)是Java 8引入的一个非常有用的特性,它允许在接口中定义带有实现的方法。

为什么需要默认方法?

在Java 8之前,接口只能包含抽象方法,即没有方法体的方法。这样在扩展接口时,如果为接口新增方法,所有实现了该接口的类都必须实现新增加的方法。这会导致大量的代码修改,尤其是在维护大型项目时非常不便。默认方法解决了这一问题。

默认方法的使用场景
  1. 接口演进:在框架或库中扩展接口功能,而不影响已有的实现。
  2. 提供常用功能:在接口中提供一些常用的工具方法,这些方法可以在实现类中直接使用或重写。
如何定义默认方法

默认方法使用 default 关键字进行定义。下面是一个简单的示例:

interface MyInterface {
    default void defaultMethod() {
        System.out.println("默认方法");
    }
    
    void abstractMethod();
}

class MyClass implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("抽象方法实现");
    }

    // 可以选择重写默认方法
    @Override
    public void defaultMethod() {
        System.out.println("重写默认方法");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.abstractMethod();  // 输出:抽象方法实现
        myClass.defaultMethod();   // 输出:重写默认方法
    }
}
多重继承中的默认方法

在使用默认方法时,可能会遇到接口多重继承的问题。如果一个类实现了多个接口,而这些接口中有同名的默认方法,那么必须在实现类中重写该默认方法,以解决冲突。

interface InterfaceA {
    default void defaultMethod() {
        System.out.println("InterfaceA 默认方法");
    }
}

interface InterfaceB {
    default void defaultMethod() {
        System.out.println("InterfaceB 默认方法");
    }
}

class MyClass implements InterfaceA, InterfaceB {
    // 必须重写 defaultMethod 以解决冲突
    @Override
    public void defaultMethod() {
        InterfaceA.super.defaultMethod();
        InterfaceB.super.defaultMethod();
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.defaultMethod();  // 输出:InterfaceA 默认方法
                                  //      InterfaceB 默认方法
    }
}

4. 新的日期时间 API (java.time)

Java 8 引入了新的日期时间 API,提供了更好的日期和时间处理。

为什么需要新的日期时间 API?

在 Java 8 之前,java.util.Datejava.util.Calendar 存在设计上的缺陷,例如线程不安全、API 使用复杂等。新的日期时间 API 提供了一种更现代化、更直观的方式来处理日期和时间。

如何使用新的日期时间 API

新的日期时间 API 位于 java.time 包下,主要包括 LocalDateLocalTimeLocalDateTimeZonedDateTime 等类。

示例

下面是一些常用的日期时间操作示例:

LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);
Period age = Period.between(birthday, today);
System.out.println("年龄: " + age.getYears() + " 年");

LocalTime now = LocalTime.now();
LocalTime bedTime = LocalTime.of(22, 0);
Duration duration = Duration.between(now, bedTime);
System.out.println("距离睡觉时间还有: " + duration.toHours() + " 小时");

Java 9 (2017)

1. 模块系统 (Project Jigsaw)

模块系统允许开发者将代码组织成独立的模块,提高代码的可维护性和封装性。

为什么需要模块系统?

在 Java 9 之前,Java 平台和应用程序通常是通过类路径管理依赖,这种方式在大型项目中容易导致类冲突和依赖管理复杂等问题。模块系统提供了一种更好的依赖管理和封装机制。

如何定义模块

模块由 module-info.java 文件定义,包含模块的名称、依赖和导出包等信息。

示例

下面是一个简单的模块定义示例:

module my.module {
    requires java.base;
    exports com.example;
}
使用模块系统的好处
  1. 强封装:模块系统强制模块之间的依赖关系,避免了类路径下的类冲突。
  2. 可维护性:通过模块化设计,代码更加清晰和可维护。
  3. 性能优化:模块化可以减少应用程序的启动时间和内存占用。

2. JShell

JShell 是一个交互式编程工具,方便开发者试验Java代码片段。

为什么需要 JShell?

在 Java 9 之前,Java 没有提供类似 Python REPL 或者 JavaScript 控制台的工具。JShell 提供了一个交互式的编程环境,可以快速测试和调试 Java 代码。

如何使用 JShell

JShell 提供了一种 REPL(Read-Eval-Print Loop)环境,可以直接输入 Java 代码并立即执行。

示例
jshell> System.out.println("你好, JShell!");

3. 改进的集合工厂方法

新的工厂方法可以更简洁地创建不可变集合。

为什么需要改进的集合工厂方法?

在 Java 9 之前,创建集合通常需要多行代码,而且创建不可变集合相对复杂。改进的集合工厂方法提供了一种更简洁的语法来创建不可变集合。

如何使用改进的集合工厂方法

新的集合工厂方法位于 ListSetMap 接口中,可以通过静态方法创建不可变集合。

示例
List<String> list = List.of("苹果", "香蕉", "橙子");
Set<String> set = Set.of("红色", "绿色", "蓝色");
Map<String, Integer> map = Map.of("张三", 1, "李四", 2);

Java 10 (2018)

1.局部变量类型推断

  • 特性: 使用 var 关键字,编译器根据右侧的值自动推断变量类型。

  • 流程:

    var list = new ArrayList<String>();
    
    • 代码中 list 的类型会被推断为 ArrayList<String>,不再需要显式声明。

2.G1 垃圾回收器改进

  • 特性: 提升了 G1 垃圾回收器的性能,优化了大堆内存的处理能力。
  • 流程:
    • 通过改进 G1 的内部算法和性能调优,减少了垃圾回收的暂停时间,提升了整体性能。

Java 11 (2018)

1. 新的字符串方法

  • 特性: 新增了 isBlanklinesstrip 方法。

    • isBlank: 检查字符串是否为空白(仅包含空格、制表符等)。
    • lines: 将字符串按行分割为流(Stream)。
    • strip: 去除字符串前后的空白字符(比 trim 更强大,支持 Unicode 空白字符)。
  • 流程:

    String str = " Hello World ";
    System.out.println(str.isBlank());  // 输出 false,因为字符串不为空白
    System.out.println(str.strip());    // 输出 "Hello World",去除了前后空格
    
    String multiLineStr = "Line1\nLine2\nLine3";
    multiLineStr.lines().forEach(System.out::println); // 输出每一行
    

2. 垃圾回收器改进

  • 特性: 引入了 ZGC (Z Garbage Collector),一个低延迟的垃圾回收器。

    • ZGC 设计用于处理大内存堆,提供低延迟的垃圾回收,适合需要高响应时间的应用。
    • 支持非常大的堆(数 TB),停顿时间通常在 10ms 以内。
  • 流程:

    • 启用 ZGC:

      java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar yourApp.jar
      
    • 这个命令行参数启用了 ZGC,以显著降低应用的垃圾回收停顿时间。

3. 单文件程序执行

  • 特性: 直接运行单个 Java 源文件。

    • 这简化了开发和测试过程,无需先编译成字节码文件,适合快速原型和简单测试。
  • 流程:

    • 创建一个简单的 Java 文件 HelloWorld.java:

      public class HelloWorld {
          public static void main(String[] args) {
              System.out.println("Hello, World!");
          }
      }
      
    • 使用以下命令直接运行这个文件:

      java HelloWorld.java
      
    • 这会在后台编译并执行 Java 文件,简化了运行单个源文件的过程。

Java 12 (2019)

1.Switch 表达式 (预览特性)

  • 特性: 将 switch 语句增强为可以作为表达式,并支持新的语法。

  • 流程:

    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY -> 7;
        default -> throw new IllegalStateException("Unexpected value: " + day);
    };
    
    • switch 表达式可以返回值,并且用箭头 (->) 语法来代替传统的 case 标签。

2.G1 垃圾回收器改进

  • 特性: 改进了 G1 垃圾回收器的吞吐量和暂停时间。
  • 流程:
    • 通过改进 G1 的并行和并发操作,减少了垃圾回收的时间,增加了吞吐量。

Java 13 (2019)

1.文本块 (预览特性)

  • 特性: 使用三重引号定义多行字符串。

  • 流程:

    String text = """
                  {
                      "name": "Alice",
                      "age": 25
                  }
                  """;
    
    • 提高了多行字符串的可读性和编写效率,特别是在处理 JSON 和 XML 时。

2.Switch 表达式 (第二次预览)

  • 特性: 对 Java 12 中的 Switch 表达式进行了进一步改进。
  • 流程:
    • 通过改进语法和功能,增强了对 Switch 表达式的支持和稳定性。

Java 14 (2020)

1. Switch 表达式

  • 特性: Switch 表达式正式引入。

    • Switch 表达式在 Java 中得到了正式支持,使得 switch 语句可以作为一种表达式返回值,从而简化代码结构和提高可读性。
    • 新的 Switch 表达式不仅支持传统的语法,还增加了更灵活和简洁的语法形式。
  • 流程:

    • 之前作为预览特性的 Switch 表达式现在作为正式特性提供了稳定的功能和语法支持。

    • 新的 Switch 表达式允许在 switch 块中使用 -> 箭头符号来代替 casebreak 关键字,使代码更加简洁明了。例如:

      int number = 3;
      String result = switch (number) {
          case 1 -> "one";
          case 2 -> "two";
          case 3 -> "three";
          default -> "unknown";
      };
      
    • 在这个示例中,Switch 表达式根据 number 的值返回相应的字符串,并赋值给 result 变量。

    • Switch 表达式还支持使用 yield 关键字返回值,使得复杂情况处理更加灵活:

      int day = 2;
      String dayType = switch (day) {
          case 1, 2, 3, 4, 5 -> "Weekday";
          case 6, 7 -> "Weekend";
          default -> {
              yield "Invalid day";
          }
      };
      
    • 在这个示例中,yield 关键字用于在默认情况下返回一个值。

    • 新的 Switch 表达式不仅可以提高代码的可读性和可维护性,还减少了冗余代码和错误的可能性。

2.instanceof 模式匹配 (预览特性)

  • 特性: 简化了 instanceof 操作符的使用。

  • 流程:

    if (obj instanceof String s) {
        System.out.println(s.toLowerCase());
    }
    
    • instanceof 现在可以直接在条件判断中进行类型转换和使用。

3.Records (预览特性)

  • 特性: 提供了一种简洁的方式定义数据类。

  • 流程:

    public record Person(String name, int age) {}
    
    • record 关键字定义了不可变的数据类,自动生成构造函数、equalshashCodetoString 方法。

Java 15 (2020)

1. 文本块

  • 特性: 文本块正式引入。

    • 文本块(Text Blocks)允许在代码中更方便地书写多行字符串,提高了代码的可读性和可维护性。
    • 使用三个双引号 """ 包围文本块,可以避免传统字符串中复杂的转义字符,同时保留原始格式。
  • 流程:

    • 之前作为预览特性的文本块现已成为正式功能,提供了稳定的多行字符串处理。

    • 文本块的主要优点包括减少了对转义字符的需求、保留了文本的原始格式以及改进了代码的可读性和可维护性。

    • 示例代码展示了如何使用文本块:

      String json = """
                    {
                        "name": "John",
                        "age": 30,
                        "city": "New York"
                    }
                    """;
      
    • 在这个示例中,文本块使得书写多行字符串(如 JSON 格式数据)更加简洁和直观,避免了传统字符串中换行符和引号的混乱。

    • 文本块还支持内置的格式修剪功能,通过去除每行前面的公共空白字符,使文本块的内容在代码中更整齐。使用 stripIndent() 方法,可以轻松移除多余的缩进:

      String html = """
                    <html>
                        <body>
                            <p>Hello, world</p>
                        </body>
                    </html>
                    """.stripIndent();
      

2.Sealed Classes (预览特性)

  • 特性: 允许开发者控制哪些类可以扩展或实现它们。

  • 流程:

    public abstract sealed class Shape
        permits Circle, Square, Rectangle {}
    
    • sealed 类限制了可以扩展或实现的类,增强了类型系统的安全性和控制。

Java 16 (2021)

1.Records

  • 特性: Records 正式引入。
  • 流程:
    • 之前的预览特性正式成为 Java 的一部分,提供稳定的数据类定义。

2.instanceof 模式匹配

  • 特性: instanceof 模式匹配正式引入。
  • 流程:
    • instanceof 操作符支持简化的类型检查和转换。

3.Sealed Classes (第二次预览)

  • 特性: Sealed Classes 进行进一步改进。
  • 流程:
    • 对 Sealed Classes 进行了改进,提供更灵活的类型系统支持。

Java 17 (2021)

1. Sealed Classes

  • 特性: Sealed Classes 正式引入。

    • Sealed Classes 允许在定义类时限制其他类对它的扩展,从而增强了类型系统的表达能力和安全性。
    • 通过控制子类的数量和类型,可以提高代码的可维护性和安全性,防止不必要的或意外的扩展。
  • 流程:

    • 在声明一个类时使用 sealed 关键字,并在 permits 子句中指定允许扩展该类的子类。例如:

      public sealed class Shape permits Circle, Square {
          // ...
      }
      
      public final class Circle extends Shape {
          // ...
      }
      
      public final class Square extends Shape {
          // ...
      }
      
    • 这种方式确保了 Shape 类只能被 CircleSquare 这两个类扩展,其他任何尝试扩展 Shape 的类都将导致编译错误。

2. 新的 LTS 版本

  • 特性: Java 17 是一个长期支持(LTS)版本。

    • Java 17 提供了更长时间的支持和维护,适用于长期使用的生产环境。这使得企业和开发者可以更安心地使用 Java 17 进行开发,而不用频繁升级到新版本。
  • 流程:

    • LTS 版本的发布周期通常为每三年一次,这意味着在这期间,LTS 版本将会得到官方的定期更新和安全补丁。
    • 企业可以在这段时间内依赖 Java 17 提供的稳定性和安全性,而不用担心频繁的版本变更带来的兼容性和维护问题。
    • 举例来说,使用 Java 17 进行项目开发,可以确保在接下来的数年内持续得到官方支持和更新,从而降低了长期的维护成本。

Java 18 (2022)

1.简化的 UTF-8 编码

  • 特性: 默认字符编码变更为 UTF-8。
  • 流程:
    • 所有的 Java 应用程序默认使用 UTF-8 编码,简化了国际化应用的开发和处理。

2.Vector API (第二次孵化)

  • 特性: 进一步改进了 Vector API,使得向量计算更加高效。
  • 流程:
    • 提供了对向量计算的支持,优化了数据处理和计算效率。

Java 19 (2022)

1.虚拟线程 (预览特性)

  • 特性: 提供了轻量级的线程实现,提升了并发处理能力。

  • 流程:

    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        executor.submit(() -> System.out.println("Hello from a virtual thread!"));
    }
    
    • 虚拟线程提供了比传统线程更轻量级的线程模型,提升了并发性能。

2.外部函数和内存 API (孵化)

  • 特性: 引入了与本地代码交互的新API。
  • 流程:
    • 新的 API 简化了 JNI 的使用,提升了与本地代码交互的效率和安全性。

Java 20 (2023)

1.模式匹配 (继续预览)

  • 特性: 进一步改进了模式匹配特性。
  • 流程:
    • 模式匹配功能进行了改进,增强了对复杂数据结构的支持。

2.字符串模板 (孵化)

  • 特性: 简化了动态字符串的生成。

  • 流程:

    String name = "Alice";
    String greeting = STR."Hello, {name}!";
    
    • 字符串模板提供了简洁的方式生成动态字符串,提升了代码的可读性和安全性。

Java 21 (2023)

1. 虚拟线程 (正式)

  • 特性: 虚拟线程在 Java 22 中正式引入。这是一种新的线程模型,旨在简化高并发编程。
  • 原理: 虚拟线程是与传统操作系统线程(平台线程)不同的一种线程模型。它们由 Java 虚拟机(JVM)管理,而不是由操作系统直接管理。虚拟线程是轻量级的,创建和管理的开销比平台线程要小很多。通过将虚拟线程映射到少量的操作系统线程上,Java 可以在单个线程中处理大量的虚拟线程,这有助于提高系统的并发处理能力。
  • 改进:
    • 性能提升: 虚拟线程可以显著减少线程切换和上下文切换的开销,使得高并发应用程序的性能得到提升。
    • 简化编程模型: 使用虚拟线程可以简化代码编写,特别是在处理大量并发任务时,可以减少复杂的线程管理代码。
    • 提升可伸缩性: 虚拟线程的轻量级特性允许应用程序处理更多的并发任务,而不会像传统线程那样消耗大量系统资源。

2. 字符串模板 (预览)

  • 特性: 字符串模板作为预览特性继续优化,提供了动态字符串处理的新功能。
  • 原理: 字符串模板是 Java 中用于生成和处理字符串的强大工具。它允许开发者通过插入变量和表达式来动态生成字符串。模板语法简洁,支持在字符串中嵌入复杂的表达式。
  • 改进:
    • 功能优化: 在预览阶段,字符串模板的功能进行了多次优化,改进了模板语法和功能,使其更加强大和灵活。
    • 增强的动态字符串处理能力: 通过字符串模板,开发者可以更方便地构造动态内容,减少了手动拼接字符串的复杂性,提高了代码的可读性和维护性。
    • 性能优化: 字符串模板的实现得到了优化,生成字符串的性能得到了提升,使得在需要大量动态字符串处理的场景中更加高效。

3. 模式匹配

  • 特性: 模式匹配在 Java 22 中得到了进一步完善,增强了对复杂数据结构的处理能力。
  • 原理: 模式匹配是一种语言特性,用于简化对对象类型和数据结构的检查和解构。它允许开发者通过模式匹配语法在一个表达式中进行类型检查和解构,而不需要编写复杂的条件语句和类型转换代码。
  • 改进:
    • 增强数据结构处理能力: Java 22 中的模式匹配扩展了对复杂数据结构(如记录和密集数据结构)的支持,使得在处理这些数据结构时更加简洁和高效。
    • 提高代码简洁性和可读性: 模式匹配使得类型检查和数据解构的代码更加简洁和易于理解,减少了样板代码的数量。
    • 改进模式匹配语法: 新版本中对模式匹配语法进行了进一步的优化和完善,使得开发者能够更直观地表达匹配逻辑,提高代码的表达力和可维护性。

Java 22 (2024)

1. 增强的垃圾回收器

ZGC (Z Garbage Collector)
  • 原理: ZGC 是一个低延迟垃圾回收器,旨在最小化垃圾回收的停顿时间。它使用并行和并发的回收策略,通过将堆分为多个区域并使用并发标记和清理过程来减少停顿时间。
  • 改进: 在 Java 22 中,ZGC 的改进可能包括优化标记和回收阶段,减少内存碎片,并提高并发处理能力,从而进一步减少应用程序的停顿时间。
G1 垃圾回收器
  • 原理: G1 是一个目标停顿时间的垃圾回收器,它将堆分为多个区域,优先回收垃圾最多的区域。它通过并行和并发回收阶段来优化停顿时间。
  • 改进: 在 Java 22 中,G1 的优化可能包括改进区域选择算法、提升并发标记和整理阶段的性能,从而减少停顿时间并提高整体性能。

2. 持续改进的外部函数和内存 API

  • 原理: 外部函数和内存 API 允许 Java 程序直接与本地代码(如 C/C++)进行交互,以及管理内存(如分配和释放)。它们提供了一种比 JNI 更高效、更安全的方式来访问本地代码和内存。
  • 改进: 在 Java 22 中,外部函数和内存 API 的改进可能包括提高访问本地代码和内存的效率,减少内存泄漏的风险,简化 API 的使用,使得与本地代码的交互更加安全和高效。

3. 新的编译器优化

  • 原理: 编译器优化包括多种技术,如优化代码生成、内联函数、循环优化等,旨在提高生成代码的执行效率。优化的目标是减少运行时开销,提高程序的整体性能。
  • 改进: 在 Java 22 中,新的编译器优化技术可能包括改进代码生成策略、提升内联优化的效果、减少冗余计算、以及更好地利用现代 CPU 特性(如 SIMD 指令集)。这些优化有助于提高 Java 程序的执行速度和响应能力。

总结

​ 从Java 8到Java 22,Java引入了众多新特性和改进,极大地提升了开发效率和性能。Lambda表达式、Stream API、模块系统、虚拟线程、字符串模板等特性,使Java在现代开发中依然保持着强大的竞争力。希望本文能帮助开发者更好地理解和利用这些新特性,在实际项目中充分发挥它们的优势。

附录

Java新特性的引入过程通常遵循一个逐步成熟的路径,从最初的概念提出到最终成为稳定的标准特性。以下是对这些概念的详细解释及其在Java发展中的作用:

  1. 预览特性
    • 定义: 预览特性是在Java开发周期中首次以实验形式推出的特性。这些特性可能尚未完全成熟,存在缺陷或不确定行为,需要通过社区的反馈来完善设计。
    • 目的: 收集开发者和用户的反馈,帮助开发团队调整和改进特性,确保其符合需求和期望。
    • 示例: Java 14中的Switch表达式,最初以预览特性形式推出,随后在后续版本中成为正式特性。
  2. 继续预览
    • 定义: 当一个预览特性在当前版本中继续保留预览状态,表明该特性需要更多时间来收集反馈和进行改进。
    • 目的: 给予更多时间来确保特性的稳定性和实用性,避免匆忙将其标准化可能带来的问题。
    • 示例: Java 20中模式匹配的继续预览,说明该特性仍在进行优化和调整。
  3. 孵化
    • 定义: 孵化特性是处于早期开发阶段的新功能,可能存在不稳定性或不完整性,其主要目标是获取社区的反馈和测试。
    • 目的: 评估特性的可行性和实用性,决定是否应进一步开发或放弃。
    • 示例: Java 19中外部函数和内存API的孵化,表明这些功能正在探索中,未来可能有重大变化。
  4. 第二次孵化
    • 定义: 特性在经历了第一次孵化后,再次进入孵化阶段,意味着需要进一步测试和改进,以确保其质量和实用性。
    • 目的: 完善特性,解决前一阶段中发现的问题,增加其成熟度。
    • 示例: Java 18中Vector API的第二次孵化,显示了对特性的深入开发和改进。
  5. 正式
    • 定义: 正式特性是经过充分测试和社区反馈后,达到稳定状态的特性,被正式纳入Java标准中。
    • 目的: 提供稳定可靠的特性供开发者在生产环境中使用,保证代码的稳定性和兼容性。
    • 示例: Java 21中虚拟线程的正式引入,标志着该特性经过多个版本的预览和孵化后,已成为稳定的标准特性。
  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值