Java 8开发与运行环境配置详解:包含JDK 8与JRE 8

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Java 8是Java语言的重要版本更新,带来了Lambda表达式、Stream API、新的日期时间API等重要特性,极大提升了开发效率与代码可读性。JDK 8作为开发核心,包含JRE及编译、调试工具;JRE 8则用于运行Java程序,提供JVM和基础类库支持。本文详细介绍JDK 8与JRE 8的组成、功能及其安装配置流程,适合初学者和开发者进行Java 8环境搭建与实践学习。
java8开发环境、运行环境包含jdk8和jre8

1. Java 8简介与新特性概述

Java 8(也称为JDK 1.8)是Java语言发展历程中一次里程碑式的升级,于2014年3月发布。它不仅提升了语言本身的表达能力,更通过引入函数式编程思想,推动了Java向更高效、更简洁的开发模式演进。本章将引导读者了解Java 8的诞生背景,并概览其核心新特性,包括:

  • Lambda表达式 :简化匿名内部类的写法,提升集合操作的可读性;
  • Stream API :提供声明式的数据处理方式,支持链式调用;
  • 新的日期时间API(java.time) :替代老旧的Date和Calendar类,提供线程安全、更易用的时间处理接口;
  • 接口默认方法与静态方法 :在不破坏已有实现的前提下扩展接口功能。

这些特性共同构成了Java 8现代化编程的基础,为后续章节深入学习打下坚实基础。

2. JDK 8安装与配置指南

在进入Java 8核心特性的学习之前,我们首先需要搭建一个稳定且兼容的开发环境。Java Development Kit(JDK)是开发Java应用程序的基础工具集,它不仅包含了Java编译器、运行时环境,还包括了调试工具、文档工具等。本章将系统地讲解如何在不同操作系统上获取、安装并配置JDK 8,并结合开发工具和命令行方式进行详细说明,确保读者能够顺利完成开发环境的搭建。

2.1 JDK 8的获取与安装

安装JDK 8是开始Java开发的第一步。Oracle官方提供了JDK 8的稳定版本,同时也存在其他开源组织如Adoptium(原AdoptOpenJDK)提供的免费发行版。选择合适的版本并正确安装是后续开发顺利进行的前提。

2.1.1 官方下载渠道与版本选择

Oracle官网提供了JDK 8的下载页面(https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html),用户可以根据自己的操作系统选择对应的版本。常见的版本包括:

操作系统 推荐JDK版本类型 文件格式
Windows JDK x64 Installer .exe
Linux JDK x64 Tar.gz .tar.gz
macOS JDK x64 DMG .dmg

⚠️ 注意:从2023年起,Oracle对JDK 8的公共更新已结束,建议使用长期支持(LTS)版本或社区维护的OpenJDK发行版,例如Adoptium(https://adoptium.net/)。

此外,不同操作系统架构(如ARM)也提供了适配版本,如用于Apple Silicon芯片的macOS版本。

2.1.2 Windows、Linux、macOS平台下的安装步骤

Windows平台安装步骤
  1. 下载JDK安装包 :访问Oracle或Adoptium官网,选择适用于Windows x64的JDK 8安装包。
  2. 运行安装程序
    - 双击下载的 .exe 文件,启动安装向导。
    - 选择安装路径(建议自定义,如 C:\Program Files\Java\jdk1.8.0_392 )。
    - 保持默认选项,点击“Next”完成安装。
  3. 验证安装
    - 打开命令提示符(CMD)。
    - 输入 java -version javac -version ,查看输出是否显示JDK 8版本信息。
Linux平台安装步骤(以Ubuntu为例)
  1. 下载JDK压缩包
    bash wget https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u392-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u392b08.tar.gz
  2. 解压到指定目录
    bash sudo mkdir -p /usr/lib/jvm sudo tar -xzf OpenJDK8U-jdk*.tar.gz -C /usr/lib/jvm
  3. 设置默认Java版本
    bash sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk8u392-b08/bin/java 1 sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk8u392-b08/bin/javac 1 sudo update-alternatives --config java sudo update-alternatives --config javac
  4. 验证安装
    bash java -version javac -version
macOS平台安装步骤
  1. 下载 .dmg 安装包 ,双击挂载。
  2. 拖动JDK图标到Applications文件夹 中完成安装。
  3. 设置默认版本 (如已安装多个JDK):
    bash /usr/libexec/java_home -V export JAVA_HOME=`/usr/libexec/java_home -v 1.8`
  4. 验证安装
    bash java -version javac -version

2.2 环境变量配置

为了确保操作系统和开发工具能够正确识别JDK的安装路径,必须配置环境变量。主要包括 JAVA_HOME PATH CLASSPATH

2.2.1 JAVA_HOME、PATH、CLASSPATH的作用与设置方法

环境变量 作用说明
JAVA_HOME 指定JDK安装目录,供其他工具调用。
PATH 包含可执行文件路径,使得系统能识别 java javac 命令。
CLASSPATH 指定Java类文件和库文件的位置(在现代Java中较少使用)。
Windows设置方法
  1. 打开“系统属性” → “高级系统设置” → “环境变量”。
  2. 添加新的系统变量:
    - 变量名: JAVA_HOME
    - 变量值: C:\Program Files\Java\jdk1.8.0_392
  3. 编辑 Path 变量,添加 %JAVA_HOME%\bin
Linux/macOS设置方法

编辑用户环境变量配置文件(如 .bashrc .zshrc ):

export JAVA_HOME=/usr/lib/jvm/jdk8u392-b08
export PATH=$JAVA_HOME/bin:$PATH

执行更新:

source ~/.bashrc  # 或 source ~/.zshrc

2.2.2 验证JDK安装是否成功

在命令行中执行以下命令:

java -version
javac -version

输出示例:

java version "1.8.0_392"
Java(TM) SE Runtime Environment (build 1.8.0_392-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.392-b08, mixed mode)

javac 1.8.0_392

如果看到类似输出,说明JDK安装成功。

2.3 开发工具集成配置

在完成JDK安装和环境变量配置后,下一步是将其集成到开发工具中,确保IDE能正确识别Java运行时和编译器。

2.3.1 在Eclipse、IntelliJ IDEA中配置JDK 8

Eclipse配置JDK 8
  1. 打开Eclipse,进入 Window → Preferences
  2. 选择 Java → Installed JREs
  3. 点击 Add... ,选择 Standard VM
  4. 点击 Directory ,浏览至JDK安装目录(如 C:\Program Files\Java\jdk1.8.0_392 )。
  5. 确认后点击 Finish ,勾选新添加的JRE并应用。
IntelliJ IDEA配置JDK 8
  1. 打开IDEA,进入 File → Project Structure
  2. SDKs 下点击 + 号,选择 JDK
  3. 浏览至JDK安装路径(如 /usr/lib/jvm/jdk8u392-b08 )。
  4. 设置Project SDK为刚刚添加的JDK 8。
  5. Project language level 中选择 8 - Lambdas, type annotations etc.
示例代码:测试JDK是否在IDE中正常工作
public class HelloJava8 {
    public static void main(String[] args) {
        // 使用Lambda表达式演示Java 8特性
        Runnable r = () -> System.out.println("Hello from Java 8!");
        new Thread(r).start();
    }
}

代码逻辑分析
- 使用了Java 8引入的Lambda表达式语法 () -> {}
- 创建一个 Runnable 实例并启动线程,输出字符串。
- 此代码可以验证IDE是否成功识别JDK 8并启用其特性。

2.3.2 使用命令行编译与运行Java程序

即使使用IDE,掌握命令行方式仍然是Java开发者必备的技能。

示例:编写并运行Java程序
  1. 创建文件 HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Java 8!");
    }
}
  1. 编译程序:
javac HelloWorld.java
  • javac 是Java编译器,将 .java 文件编译成 .class 字节码文件。
  1. 运行程序:
java HelloWorld
  • java 是Java运行时命令,执行编译后的类。
参数说明:
  • javac 常用参数:
  • -d <dir> :指定输出目录。
  • -source :指定源码版本(如 -source 1.8 )。
  • java 常用参数:
  • -cp -classpath :指定类路径。
  • -Xmx :设置最大堆内存(如 -Xmx512m )。
流程图:Java编译与运行流程
graph TD
    A[编写Java源代码] --> B[javac编译]
    B --> C[生成.class文件]
    C --> D[java运行]
    D --> E[输出结果]

本章从JDK 8的下载与安装入手,详细介绍了在不同操作系统下的安装步骤,环境变量的配置方法,以及在主流IDE中集成JDK的过程。通过示例代码和命令行演示,帮助开发者快速搭建起Java 8的开发环境,为后续学习Java 8新特性打下坚实基础。

3. JRE 8安装与运行环境配置

Java Runtime Environment(JRE)8 是 Java 8 平台的核心运行组件,为 Java 应用程序提供必要的类库、运行时支持和 Java 虚拟机(JVM)。与 JDK(Java Development Kit)不同,JRE 主要面向最终用户和应用程序运行环境,而非开发者。本章将详细解析 JRE 8 的安装部署流程、运行环境的配置方式,以及 JVM 启动参数的调优策略,帮助读者掌握如何在多种操作系统中高效配置 Java 运行环境,并为后续的应用部署打下坚实基础。

3.1 JRE 8的安装与部署

JRE 8 的安装是确保 Java 应用能够正常运行的第一步。在部署之前,理解 JRE 与 JDK 的区别至关重要,这有助于根据具体需求选择合适的版本。

3.1.1 JRE与JDK的区别

JRE(Java Runtime Environment)与 JDK(Java Development Kit)的主要区别在于功能定位不同:

特性 JRE 8 JDK 8
主要用途 运行 Java 应用 开发 Java 应用
包含内容 JVM + 类库 JRE + 编译器(javac)、调试工具等
安装对象 普通用户或服务器环境 开发人员
是否支持编译
系统资源占用 较低 较高
安装包大小

适用场景
- JRE 适用于仅需运行 Java 应用程序的环境,例如服务器、终端用户设备等。
- JDK 适用于开发人员、构建系统、测试环境等需要编译、调试的场景。

3.1.2 不同操作系统下的JRE安装流程

Windows 平台安装 JRE 8
  1. 访问 Oracle 官网
    打开 https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html ,选择适用于 Windows 的 JRE 安装包。

  2. 下载安装包
    下载后得到一个 .exe 文件,例如 jre-8u291-windows-x64.exe

  3. 运行安装程序
    双击安装程序,按照提示选择安装路径、组件(如是否安装浏览器插件等),完成安装。

  4. 验证安装
    打开命令行,输入以下命令:

java -version

输出应类似:

java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)
Linux 平台安装 JRE 8
  1. 下载 RPM 或 tar.gz 包
    例如 jre-8u291-linux-x64.tar.gz

  2. 解压安装包

tar -xzvf jre-8u291-linux-x64.tar.gz -C /opt/
  1. 设置环境变量
export JAVA_HOME=/opt/jre1.8.0_291
export PATH=$JAVA_HOME/bin:$PATH
  1. 验证安装
java -version
macOS 安装 JRE 8
  1. 下载 .pkg 安装包,例如 jre-8u291-macosx-x64.dmg

  2. 挂载 DMG 文件后,双击 .pkg 文件,按照安装向导完成安装。

  3. 验证安装:

/usr/libexec/java_home -V

3.2 运行环境的配置与优化

安装完 JRE 后,还需根据具体应用场景配置运行环境,尤其是浏览器插件的安全策略和 Java 安全策略文件。

3.2.1 Java插件在浏览器中的配置

Java 插件主要用于在浏览器中运行 Java Applet。虽然现代浏览器已逐步淘汰 Applet 支持,但在某些遗留系统中仍需启用。

启用 Java 浏览器插件(以 Windows 为例):
  1. 打开 控制面板 > Java

  2. 在“安全”选项卡中,勾选“启用浏览器中的 Java 内容”。

  3. 在“高级”选项卡中,可设置默认的 Java 控制面板行为,例如是否提示用户运行 Applet。

禁用插件(推荐):

由于安全风险较高,建议禁用插件:

graph TD
A[打开 Java 控制面板] --> B[安全选项卡]
B --> C[取消勾选 "启用浏览器中的 Java 内容"]
C --> D[点击应用]

3.2.2 设置Java安全策略文件

Java 安全策略文件用于定义 Java 应用程序在运行时的权限限制。默认策略文件位于 $JAVA_HOME/lib/security/java.policy

自定义安全策略文件示例:

创建一个名为 my.policy 的文件:

grant {
    permission java.io.FilePermission "<<ALL FILES>>", "read,write";
    permission java.net.SocketPermission "*:1024-65535", "connect,resolve";
};
启动时指定策略文件:
java -Djava.security.manager -Djava.security.policy==my.policy MyApp

⚠️ 注意: == 表示替换默认策略,单个 = 表示追加。

3.3 Java运行参数调优

合理配置 JVM 参数可以显著提升 Java 应用的性能和稳定性。JVM 启动参数主要包括内存设置、垃圾回收器选择、性能监控等。

3.3.1 常用JVM启动参数解析

参数 描述
-Xms<size> 初始堆内存大小(例如:-Xms512m)
-Xmx<size> 最大堆内存大小(例如:-Xmx2g)
-Xss<size> 线程栈大小(例如:-Xss1m)
-XX:NewRatio=<n> 年轻代与老年代比例(默认为2,即1:2)
-XX:+UseParallelGC 使用并行垃圾回收器(吞吐量优先)
-XX:+UseConcMarkSweepGC 使用 CMS 垃圾回收器(低延迟优先)
-XX:+PrintGCDetails 输出 GC 详细信息
-XX:+PrintGCDateStamps 输出 GC 时间戳
-Xloggc:<file> 指定 GC 日志输出文件
-verbose:gc 输出 GC 信息
示例:启动应用并启用 GC 日志
java -Xms512m -Xmx2g -XX:+UseParallelGC -Xloggc:/var/log/myapp_gc.log -verbose:gc MyApp

3.3.2 内存管理与性能优化建议

堆内存分配建议:
  • 小规模应用 -Xms256m -Xmx512m
  • 中等规模应用 -Xms1g -Xmx4g
  • 大规模应用 -Xms4g -Xmx16g
垃圾回收器选择建议:
应用类型 推荐垃圾回收器 特点说明
高吞吐应用 Parallel Scavenge + Serial Old 吞吐量优先,适合后台处理
低延迟要求 G1 GC 可控停顿时间,适用于 Web 服务等实时响应场景
遗留系统 CMS GC 低延迟但易发生并发模式失败
示例:G1 垃圾回收器配置
java -Xms4g -Xmx16g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 MyApp

参数说明:
- -XX:+UseG1GC :启用 G1 垃圾回收器。
- -XX:MaxGCPauseMillis=200 :控制最大 GC 停顿时间为 200 毫秒以内。

性能监控建议:

使用以下参数启用 JMX 监控以进行远程性能分析:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=12345
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

安全提示:生产环境应启用认证和 SSL 加密。

小结

通过本章的学习,读者应掌握 JRE 8 的安装部署方法、运行环境配置技巧,以及 JVM 启动参数的调优策略。JRE 是 Java 应用运行的基础,合理配置和调优可显著提升系统性能和稳定性。下一章将深入探讨 Lambda 表达式,它是 Java 8 最具变革性的特性之一,将极大简化函数式编程的实现方式。

4. Lambda表达式语法与应用

Lambda表达式是 Java 8 最具革命性的新特性之一,它为 Java 引入了函数式编程的风格,极大地简化了代码结构,提高了代码的可读性和开发效率。通过 Lambda 表达式,开发者可以将函数作为参数传递给方法,也可以将代码逻辑作为数据进行操作。本章将深入探讨 Lambda 表达式的基本语法、在集合操作中的应用,以及其在线程安全和性能方面的考量,帮助读者全面掌握这一强大的新特性。

4.1 Lambda表达式的基本语法结构

4.1.1 函数式接口与Lambda的关系

Java 8 引入了“函数式接口(Functional Interface)”的概念,这是 Lambda 表达式得以实现的基础。函数式接口是指 只有一个抽象方法 的接口,例如 java.util.function.Consumer java.util.function.Supplier java.util.function.Predicate 等。

Lambda 表达式本质上是一个 匿名函数 ,它必须与某个函数式接口的抽象方法在 参数类型和返回类型 上匹配。这种匹配关系使得 Lambda 可以被当作接口实例来使用。

示例代码:
@FunctionalInterface
interface Greeting {
    void sayHello(String name);
}

public class LambdaDemo {
    public static void main(String[] args) {
        Greeting greeting = (name) -> System.out.println("Hello, " + name);
        greeting.sayHello("Alice");
    }
}
代码分析:
  • @FunctionalInterface 注解用于标识一个接口是函数式接口。
  • (name) -> System.out.println(...) 是一个 Lambda 表达式,它实现了 sayHello 方法。
  • Lambda 表达式可以省略参数类型,由编译器自动推断。
  • greeting.sayHello("Alice") 调用了 Lambda 表达式实现的方法。
参数说明:
  • name :Lambda 表达式的参数,对应 sayHello 方法的参数。
  • System.out.println(...) :Lambda 的函数体,表示执行的逻辑。

4.1.2 参数类型推断与简化写法

Lambda 表达式的一个重要特性是 类型推断(Type Inference) ,即编译器可以自动推断出参数的类型,无需显式声明。

示例代码:
List<String> names = Arrays.asList("John", "Jane", "Doe");

// 完整写法
names.forEach((String name) -> System.out.println(name));

// 简化写法
names.forEach(name -> System.out.println(name));
代码分析:
  • 第一个 Lambda 表达式显式声明了参数类型为 String
  • 第二个 Lambda 表达式省略了类型声明,编译器根据 List<String> 推断出 name String 类型。
  • 这种简化写法使得代码更加简洁且易于阅读。
参数说明:
  • name :参数名,可以任意命名。
  • -> :箭头符号,分隔参数和函数体。
  • System.out.println(name) :Lambda 的执行体。

4.2 Lambda表达式在集合操作中的应用

4.2.1 替代匿名内部类

在 Java 8 之前,开发者通常使用匿名内部类来实现接口方法,这种方式代码冗长且难以维护。Lambda 表达式提供了一种更简洁的替代方式。

对比示例:

使用匿名内部类:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread running...");
    }
}).start();

使用 Lambda 表达式:

new Thread(() -> System.out.println("Thread running...")).start();
代码分析:
  • 匿名内部类需要显式实现 Runnable 接口的 run() 方法。
  • Lambda 表达式则直接写出函数体,省略了接口实现的冗余代码。
  • 更简洁,逻辑清晰,易于维护。

4.2.2 遍历、排序与过滤操作实战

Java 8 的集合类(如 List Set )都支持 forEach() stream() 等方法,结合 Lambda 表达式可以实现高效的数据操作。

示例:遍历与排序
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 遍历
names.forEach(name -> System.out.println(name));

// 排序
names.sort((a, b) -> a.compareTo(b));
System.out.println("Sorted names: " + names);
输出结果:
Alice
Bob
Charlie
David
Sorted names: [Alice, Bob, Charlie, David]
代码分析:
  • forEach :遍历集合中的每个元素并执行 Lambda 表达式。
  • sort :使用 Lambda 表达式定义排序逻辑, a.compareTo(b) 是按自然顺序排序。
  • Lambda 表达式简化了排序逻辑的编写,代码更直观。
示例:过滤操作
List<Integer> numbers = Arrays.asList(10, 15, 20, 25, 30);

// 过滤出大于 20 的数字
List<Integer> filtered = numbers.stream()
        .filter(n -> n > 20)
        .collect(Collectors.toList());

System.out.println("Filtered numbers: " + filtered);
输出结果:
Filtered numbers: [25, 30]
代码分析:
  • filter(n -> n > 20) :Lambda 表达式作为参数传入 filter 方法,用于筛选符合条件的元素。
  • stream() :将集合转换为流,以便进行链式操作。
  • collect(Collectors.toList()) :将过滤后的结果收集为新的 List
Lambda 在集合操作中的优势:
特性 匿名内部类 Lambda 表达式
冗余程度 高,需显式实现接口 低,直接写函数体
可读性 低,代码结构复杂 高,逻辑清晰
可维护性 低,难以修改 高,修改简单
编写效率

4.3 Lambda表达式的线程安全与性能考量

4.3.1 多线程环境下使用Lambda的注意事项

虽然 Lambda 表达式简化了并发编程的代码结构,但在多线程环境下使用时仍需注意以下几点:

  1. 状态共享问题 :如果 Lambda 表达式访问了外部变量,而该变量在多个线程中被修改,则可能导致线程安全问题。
  2. 副作用控制 :避免在 Lambda 表达式中执行有副作用的操作(如修改共享变量),以防止竞态条件。
  3. 不可变性原则 :尽量使用不可变对象或局部变量,以提高线程安全性。
示例代码:
List<String> sharedList = new ArrayList<>();
ExecutorService executor = Executors.newFixedThreadPool(2);

executor.submit(() -> {
    sharedList.add("Task 1");
});

executor.submit(() -> {
    sharedList.add("Task 2");
});

executor.shutdown();
分析:
  • sharedList 是共享变量,两个线程同时向其中添加元素,存在并发修改的风险。
  • 应使用线程安全的集合类如 CopyOnWriteArrayList 或进行同步控制。
解决方案:
List<String> safeList = Collections.synchronizedList(new ArrayList<>());

4.3.2 Lambda表达式对代码性能的影响分析

虽然 Lambda 表达式提升了代码的可读性和开发效率,但在性能方面也有一些潜在影响:

  1. 内存开销 :每个 Lambda 表达式都会被编译成一个内部类或使用 invokedynamic 指令生成的类,会增加一定的内存开销。
  2. 调用开销 :Lambda 表达式在运行时会通过 invokevirtual invokestatic 调用目标方法,比直接方法调用稍慢。
  3. 流式操作性能 :使用 Stream API 时,尤其是并行流,需要注意数据量和线程调度的开销。
性能对比示例:
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    numbers.add(i);
}

// Lambda + Stream
long start = System.currentTimeMillis();
numbers.stream().filter(n -> n % 2 == 0).count();
long end = System.currentTimeMillis();
System.out.println("Stream time: " + (end - start) + " ms");

// 普通循环
start = System.currentTimeMillis();
int count = 0;
for (int n : numbers) {
    if (n % 2 == 0) count++;
}
end = System.currentTimeMillis();
System.out.println("Loop time: " + (end - start) + " ms");
输出结果(示例):
Stream time: 120 ms
Loop time: 30 ms
分析:
  • 对于小数据量,Stream API 的性能与普通循环接近。
  • 对于大数据量,Stream API 的性能略逊于普通循环,但代码更简洁、逻辑更清晰。
  • 可通过并行流优化大数据处理性能,但需权衡线程调度成本。
Lambda 性能优化建议:
优化策略 说明
使用并行流 对于大数据集,使用 parallelStream() 可提升处理效率
避免在 Lambda 中频繁创建对象 减少垃圾回收压力
合理使用缓存 对于重复计算的逻辑,可使用缓存机制
谨慎使用捕获变量 捕获变量会增加 Lambda 的内存开销

小结

本章深入讲解了 Lambda 表达式的基本语法、在集合操作中的实际应用,以及其在多线程环境下的注意事项和性能影响。通过 Lambda 表达式,Java 开发者能够编写出更加简洁、高效的函数式代码,提升开发效率和代码质量。下一章将继续探讨 Lambda 的高级应用——方法引用与构造器引用,进一步提升代码的简洁性与可维护性。

5. 方法引用与构造器引用实战

Java 8在引入Lambda表达式的同时,也带来了方法引用(Method Reference)和构造器引用(Constructor Reference)这两个非常实用的语法特性。它们的核心目标是简化代码,提高可读性和可维护性,尤其是在结合Stream API进行函数式编程时,能够显著减少样板代码(boilerplate code)。

在本章中,我们将从方法引用的基本形式出发,深入探讨其语法结构与使用方式,接着介绍构造器引用的语法与实际应用,最后通过实战案例展示方法引用与Lambda表达式如何协同工作,以提升代码的简洁性和效率。

5.1 方法引用的基本形式

方法引用是Lambda表达式的一种简化形式,它允许我们直接引用已有的方法,而不是显式地写出方法体。Java 8支持三种主要的方法引用形式:

  • 类名::静态方法
  • 对象名::实例方法
  • 类名::实例方法

这些形式虽然看起来简单,但它们在实际开发中具有广泛的用途,尤其是在操作集合和使用Stream API时。

5.1.1 类名::静态方法

这种形式用于引用某个类的静态方法。它的语法为:

ClassName::staticMethodName
示例代码:
import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // 使用Lambda表达式
        names.forEach(name -> System.out.println(name));

        // 使用方法引用
        names.forEach(System.out::println);
    }
}
代码分析:
  • System.out::println 是一个方法引用,它引用了 PrintStream 类的 println 方法。
  • 由于 System.out 是一个 PrintStream 实例,所以这里可以理解为 instance::method ,但 System.out 是静态变量,所以也可以视为类名调用。
  • 与Lambda表达式相比,方法引用更加简洁,且语义清晰。
参数说明:
  • name -> System.out.println(name) :Lambda表达式,接收一个字符串参数并打印。
  • System.out::println :方法引用,等价于上述Lambda表达式。

5.1.2 对象名::实例方法

这种形式用于引用某个对象的实例方法,语法为:

objectInstance::instanceMethodName
示例代码:
import java.util.Arrays;
import java.util.List;

class Printer {
    public void print(String message) {
        System.out.println("Printer: " + message);
    }
}

public class MethodReferenceExample {
    public static void main(String[] args) {
        Printer printer = new Printer();
        List<String> messages = Arrays.asList("Hello", "World", "Java 8");

        // Lambda表达式
        messages.forEach(msg -> printer.print(msg));

        // 方法引用
        messages.forEach(printer::print);
    }
}
代码分析:
  • printer::print 引用了 Printer 类的 print 方法。
  • 方法引用的写法比Lambda表达式更简洁,且更容易理解。
参数说明:
  • msg -> printer.print(msg) :Lambda表达式传递每个字符串给 printer print 方法。
  • printer::print :直接引用该对象的方法,语法更紧凑。

5.1.3 类名::实例方法

这种形式用于引用某个类的实例方法,但该方法的第一个参数会作为调用对象。语法为:

ClassName::instanceMethodName
示例代码:
import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("apple", "banana", "cherry");

        // Lambda表达式
        names.stream()
             .map(name -> name.toUpperCase())
             .forEach(System.out::println);

        // 方法引用
        names.stream()
             .map(String::toUpperCase)
             .forEach(System.out::println);
    }
}
代码分析:
  • String::toUpperCase 是类名调用实例方法的形式。
  • 在这种情况下,每个 name 就是调用 toUpperCase() 的对象。
  • 这种方式特别适用于Stream操作中的映射和转换。
参数说明:
  • name -> name.toUpperCase() :Lambda表达式将每个字符串转换为大写。
  • String::toUpperCase :方法引用形式,等价于上述Lambda表达式。

5.2 构造器引用与对象创建

除了方法引用,Java 8还引入了构造器引用这一特性,它允许我们通过引用类的构造方法来创建对象。构造器引用常用于工厂模式、集合初始化等场景。

5.2.1 构造器引用的语法格式

构造器引用的语法形式为:

ClassName::new

它会根据上下文推断出调用哪个构造函数。

示例代码:
import java.util.function.Supplier;

class Person {
    private String name;

    public Person() {
        this.name = "Unknown";
    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}

public class ConstructorReferenceExample {
    public static void main(String[] args) {
        // 使用Lambda表达式创建对象
        Supplier<Person> supplier1 = () -> new Person();
        System.out.println(supplier1.get());

        // 使用构造器引用
        Supplier<Person> supplier2 = Person::new;
        System.out.println(supplier2.get());
    }
}
代码分析:
  • Person::new 是构造器引用,根据 Supplier 接口的定义,它没有参数,因此匹配的是无参构造函数。
  • 如果我们有多个构造函数,Java会根据函数式接口的参数类型自动选择合适的构造函数。
参数说明:
  • () -> new Person() :Lambda表达式调用无参构造器。
  • Person::new :构造器引用,等价于上述表达式。

5.2.2 使用构造器引用简化对象创建过程

构造器引用在集合初始化中也十分有用,尤其是在结合 Stream Collectors.toMap 时。

示例代码:
import java.util.*;
import java.util.stream.Collectors;

class Product {
    private int id;
    private String name;

    public Product(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Product{id=" + id + ", name='" + name + "'}";
    }
}

public class ConstructorReferenceExample {
    public static void main(String[] args) {
        Map<Integer, String> productMap = new HashMap<>();
        productMap.put(1, "Laptop");
        productMap.put(2, "Phone");
        productMap.put(3, "Tablet");

        // 使用构造器引用创建Product对象
        Map<Integer, Product> products = productMap.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> new Product(entry.getKey(), entry.getValue())
                ));

        // 使用构造器引用替代Lambda
        Map<Integer, Product> productsRef = productMap.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Product::new
                ));

        System.out.println(productsRef);
    }
}
代码分析:
  • Product::new 被用于构造函数匹配 BiFunction apply 方法。
  • 它的参数是 Integer String ,正好与 Product(int id, String name) 构造函数匹配。
  • 使用构造器引用后,代码更简洁,且可读性更强。
参数说明:
  • entry -> new Product(entry.getKey(), entry.getValue()) :Lambda表达式手动调用构造器。
  • Product::new :构造器引用自动匹配合适的构造函数。

5.3 方法引用与Lambda表达式的结合使用

方法引用与Lambda表达式并非彼此孤立,它们可以相互配合,共同提升代码的可读性和执行效率。本节将通过实际案例展示如何在真实项目中结合使用两者。

5.3.1 提高代码简洁性与可维护性

在Java中,尤其是处理集合时,方法引用与Lambda表达式的结合使用可以极大提升代码的简洁性和可维护性。

示例代码:
import java.util.*;
import java.util.stream.Collectors;

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "'}";
    }
}

public class LambdaAndMethodReference {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // 使用Lambda表达式创建User对象
        List<User> users = names.stream()
                .map(name -> new User(name))
                .collect(Collectors.toList());

        // 使用构造器引用替代Lambda
        List<User> usersRef = names.stream()
                .map(User::new)
                .collect(Collectors.toList());

        System.out.println(usersRef);
    }
}
代码分析:
  • map(name -> new User(name)) 是标准的Lambda表达式。
  • map(User::new) 是构造器引用,更简洁且易于维护。
  • 两者的功能完全相同,但后者语法更优雅。
参数说明:
  • name -> new User(name) :Lambda表达式逐个创建对象。
  • User::new :构造器引用,根据参数自动匹配构造函数。

5.3.2 实战案例:使用方法引用重构传统回调函数

在Java 8之前,我们常常使用匿名内部类来实现回调函数,例如事件监听器、线程启动等。现在我们可以使用方法引用进行重构。

示例代码:
import javax.swing.*;
import java.awt.event.ActionEvent;

public class CallbackRefactoring {
    public void onButtonClick(ActionEvent e) {
        System.out.println("按钮被点击!");
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Method Reference Example");
        JButton button = new JButton("点击我");

        CallbackRefactoring example = new CallbackRefactoring();

        // 传统匿名内部类
        button.addActionListener(e -> example.onButtonClick(e));

        // 使用方法引用
        button.addActionListener(example::onButtonClick);

        frame.add(button);
        frame.setSize(300, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}
代码分析:
  • example::onButtonClick 是一个方法引用,用于替代 e -> example.onButtonClick(e)
  • 在GUI编程中,这种方法引用可以显著减少样板代码,使代码更清晰。
参数说明:
  • e -> example.onButtonClick(e) :Lambda表达式调用实例方法。
  • example::onButtonClick :方法引用形式,语法更简洁。

总结性分析与对比表格

特性 Lambda表达式 方法引用 构造器引用
语法 (params) -> expression ClassName::methodName ClassName::new
可读性 一般
适用场景 任意逻辑 简单调用已有方法 创建对象
性能 相同 相同 相同
代码量 较多

流程图:方法引用与Lambda表达式结合使用的执行流程

graph TD
    A[开始] --> B[定义集合或流]
    B --> C{是否需要调用已有方法?}
    C -->|是| D[使用方法引用 ClassName::method]
    C -->|否| E[使用Lambda表达式 (params) -> methodCall]
    D --> F[执行流操作]
    E --> F
    F --> G[输出结果]

本章通过理论与实践相结合的方式,详细讲解了Java 8中方法引用与构造器引用的语法结构、应用场景及与Lambda表达式的协同使用。通过本章的学习,读者应能够熟练运用这些新特性,编写出更加简洁、高效且易于维护的Java代码。

6. 接口默认方法设计与实现

Java 8 引入的接口默认方法(Default Methods)是其面向对象设计中的一次重要革新。它打破了传统接口只能定义方法签名、不能提供方法实现的限制,为接口提供了默认实现,使得接口在版本升级时具备更强的向后兼容能力。本章将深入探讨接口默认方法的语法结构、设计原则、使用场景,以及在多继承时可能遇到的冲突问题和解决策略。

6.1 默认方法的概念与语法

默认方法是 Java 8 中接口支持的一种新特性,它允许在接口中定义带有默认实现的方法。这种设计不仅提高了接口的灵活性,也为 Java 的库开发者提供了更强大的演进能力。

6.1.1 默认方法的定义方式

默认方法的语法格式如下:

public interface MyInterface {
    // 抽象方法
    void abstractMethod();

    // 默认方法
    default void defaultMethod() {
        System.out.println("This is a default method.");
    }
}
  • default 关键字用于声明一个默认方法。
  • 默认方法可以有具体的方法体,因此实现类可以继承并直接使用该方法,而无需强制重写。
  • 实现类可以选择重写默认方法,以提供更具体的行为。
示例分析:
public class MyClass implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("Implementing abstract method.");
    }

    // 可以选择不重写 defaultMethod()
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.abstractMethod();  // 输出:Implementing abstract method.
        obj.defaultMethod();   // 输出:This is a default method.
    }
}

逻辑分析:
- MyClass 实现了 MyInterface ,并重写了抽象方法 abstractMethod()
- 没有重写 defaultMethod() ,因此调用的是接口中定义的默认实现。
- 这种机制允许接口在不破坏现有实现类的前提下新增功能。

6.1.2 接口中的默认方法与静态方法对比

特性 默认方法(default method) 静态方法(static method)
定义方式 使用 default 关键字 使用 static 关键字
调用方式 通过实现类的对象调用 通过接口名直接调用
是否可被重写
是否属于对象行为
使用场景 提供接口方法的默认实现 提供与接口相关的工具方法
示例代码:
public interface UtilityInterface {
    default void instanceMethod() {
        System.out.println("Instance method via object.");
    }

    static void staticMethod() {
        System.out.println("Static method via interface.");
    }
}

public class Main {
    public static void main(String[] args) {
        UtilityInterface obj = new UtilityInterface() {};
        obj.instanceMethod();  // 输出:Instance method via object.
        UtilityInterface.staticMethod();  // 输出:Static method via interface.
    }
}

逻辑分析:
- instanceMethod() 是默认方法,必须通过接口的实现类实例来调用。
- staticMethod() 是静态方法,可以直接通过接口名调用。
- 默认方法更适合作为接口行为的扩展,而静态方法更适合封装工具方法。

6.2 默认方法的设计原则与使用场景

默认方法的引入不仅是语法上的增强,更是为了解决接口演化过程中的兼容性问题。它的设计背后有一套清晰的原则和适用场景。

6.2.1 向后兼容的接口升级策略

Java 长期以来一直强调接口的稳定性。一旦接口发布,其所有实现类都必须满足接口的定义。然而,在 Java 8 之前,如果接口需要新增方法,所有实现类都必须提供实现,否则编译失败。

默认方法的出现解决了这一问题。通过提供默认实现,接口可以在不破坏已有实现的前提下新增功能。

示例:接口升级
// 接口旧版本
public interface OldVersion {
    void doSomething();
}

// 接口升级版本
public interface NewVersion {
    void doSomething();

    default void doSomethingElse() {
        System.out.println("New method with default implementation.");
    }
}

逻辑分析:
- OldVersion 接口在升级为 NewVersion 后新增了 doSomethingElse() 方法。
- 由于是默认方法,所有实现了 NewVersion 的类无需修改即可继续运行。
- 这种机制使得库开发者可以安全地扩展接口功能。

6.2.2 默认方法在库设计中的实际应用

Java 8 中的集合框架广泛使用了默认方法。例如, java.util.Collection java.util.List 接口都新增了许多默认方法,如 forEach() removeIf() 等。

示例: forEach() 方法
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // 使用默认方法 forEach
        names.forEach(name -> System.out.println(name));
    }
}

逻辑分析:
- forEach() Iterable 接口中定义的默认方法。
- 所有实现了 Iterable 的集合类(如 List Set )都可以直接调用该方法。
- 无需每个集合类单独实现该方法,大大减少了重复代码。

6.3 多继承与冲突解决机制

Java 8 中引入默认方法后,接口的多继承机制变得更加灵活,但也带来了新的问题: 当多个接口定义了相同签名的默认方法时,如何处理冲突?

6.3.1 多个接口定义相同默认方法的冲突处理

当一个类实现了多个接口,而这些接口中定义了具有相同方法签名的默认方法时,Java 编译器会抛出编译错误,并要求开发者显式解决冲突。

示例冲突场景:
interface A {
    default void show() {
        System.out.println("From A");
    }
}

interface B {
    default void show() {
        System.out.println("From B");
    }
}

class ConflictClass implements A, B {
    // 编译错误:需要显式解决冲突
}

解决方案:

class ConflictClass implements A, B {
    @Override
    public void show() {
        A.super.show();  // 调用 A 接口的默认方法
    }
}

逻辑分析:
- 当两个接口都提供相同签名的默认方法时,Java 编译器无法自动决定使用哪一个。
- 开发者必须在实现类中重写该方法,并明确指定调用哪一个接口的默认实现。
- 使用 接口名.super.方法名() 的方式可以访问接口的默认方法。

6.3.2 子类如何覆盖默认方法

子类不仅可以解决接口之间的冲突,还可以选择覆盖默认方法,提供更具体的实现。

示例:
interface Animal {
    default void speak() {
        System.out.println("Animal speaks");
    }
}

class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.speak();  // 输出:Dog barks
    }
}

逻辑分析:
- Dog 类覆盖了 Animal 接口中的 speak() 默认方法。
- 子类可以通过重写方式完全替换默认实现,提供更符合业务需求的行为。

总结性流程图:默认方法的调用与冲突处理流程

graph TD
    A[类实现多个接口] --> B{是否包含相同默认方法?}
    B -->|否| C[调用各自接口的默认方法]
    B -->|是| D[必须显式重写冲突方法]
    D --> E[使用 Interface.super.method() 调用特定接口的方法]

说明:
- 上图展示了默认方法在多继承时的处理流程。
- 若多个接口定义了相同签名的默认方法,则必须在实现类中显式解决冲突。
- 开发者可以通过 接口名.super.方法名() 的方式调用特定接口的默认实现。

总结性表格:默认方法与抽象类的对比

对比项 默认方法(接口) 抽象类
是否可以有实现 是(默认方法)
是否可以有构造函数
是否支持多继承 是(接口) 否(类只能单继承)
是否可以定义字段 否(只能是常量)
是否可以定义 private 方法 Java 9+ 支持默认方法中调用 private 方法

说明:
- 默认方法让接口具备了更接近抽象类的能力。
- 但在状态管理、构造函数、访问控制等方面,抽象类依然具有优势。
- 开发者应根据设计需求合理选择使用接口默认方法还是抽象类。

本章深入探讨了 Java 8 接口中引入的默认方法机制,包括其语法定义、设计原则、使用场景,以及在多继承环境下的冲突解决策略。默认方法的引入不仅提升了接口的灵活性和可维护性,也为 Java 的库开发者提供了更强的演化能力。下一章将继续深入 Java 8 的另一大核心特性 —— Stream API,探讨其在数据处理中的强大功能。

7. Stream API数据处理实战

Java 8 引入的 Stream API 是函数式编程风格的重要体现,它极大地简化了集合数据的操作,使代码更简洁、易读、富有表达力。本章将围绕 Stream 的核心概念、常用操作、性能优化与实战案例展开,帮助开发者掌握如何高效地使用 Stream 处理数据流。

7.1 Stream API的核心概念与操作流程

Stream 并不是一种数据结构,而是一种对集合(Collection)进行操作的抽象接口。它支持链式调用,允许我们以声明式的方式处理集合数据。

7.1.1 Stream的创建与中间操作

创建 Stream 的方式多种多样,常见的方式如下:

// 从集合创建
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> stream = list.stream();

// 从数组创建
int[] numbers = {1, 2, 3, 4};
IntStream intStream = Arrays.stream(numbers);

// 使用 Stream.of 创建
Stream<String> streamOf = Stream.of("one", "two", "three");

中间操作(Intermediate operations)不会立即执行,而是返回一个新的 Stream,常见的中间操作包括:

  • filter() :过滤符合条件的元素
  • map() :将每个元素映射为另一个对象
  • sorted() :排序
  • limit() :限制结果数量
  • skip() :跳过前N个元素
  • distinct() :去重

这些操作可以链式调用,例如:

list.stream()
    .filter(s -> s.length() > 5)
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

7.1.2 终止操作与结果收集

终止操作(Terminal operations)会触发实际的数据处理,并返回结果或副作用。常见的终止操作有:

  • forEach() :遍历元素
  • collect() :收集结果到容器
  • reduce() :聚合操作
  • count() :统计元素个数
  • anyMatch() allMatch() :条件判断
List<String> filteredList = list.stream()
    .filter(s -> s.startsWith("a"))
    .collect(Collectors.toList());

7.2 Stream的常用操作与函数式编程技巧

Stream 的操作丰富多样,结合函数式编程可以写出极具表现力的代码。

7.2.1 filter、map、flatMap、sorted等操作详解

  • filter :用于筛选符合条件的元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> even = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
  • map :用于将每个元素转换为另一个对象。
List<String> upperCase = list.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
  • flatMap :用于扁平化流,将多个流合并为一个流。
List<List<String>> lists = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);

List<String> flat = lists.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
  • sorted :用于对流中的元素排序。
List<String> sorted = list.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());

7.2.2 使用reduce进行聚合计算

reduce 是一种常见的聚合操作,用于将流中的元素组合成一个最终结果。

Optional<Integer> sum = numbers.stream()
    .reduce(Integer::sum);

Integer total = numbers.stream()
    .reduce(0, Integer::sum); // 提供初始值

reduce 也常用于查找最大值、最小值等:

Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);

7.3 并行流与性能优化

Stream 支持并行处理(Parallel Stream),利用多核 CPU 提升处理效率。

7.3.1 并行流的执行机制

并行流内部使用 Fork/Join 框架进行任务拆分与合并,适用于大量数据的处理。

int sum = numbers.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();

并行流的处理过程如下(mermaid流程图):

graph TD
    A[开始处理] --> B[数据分片]
    B --> C[Fork: 子任务并行]
    C --> D[Join: 合并结果]
    D --> E[返回最终结果]

7.3.2 并行流的适用场景与性能对比

场景 串行流性能 并行流性能 是否推荐使用
小数据量(<1000)
中等数据量(1000~10000) 一般 较快 一般
大数据量(>10000)

注意事项:
- 并行流不适合所有场景,尤其是数据量小或操作本身耗时短的情况。
- 并行流是无序的,除非显式调用 unordered() 或者使用 forEachOrdered()

7.4 实战案例:使用Stream处理复杂数据结构

7.4.1 数据过滤与转换的综合应用

假设我们有如下用户类:

class User {
    private String name;
    private int age;
    private boolean active;

    // 构造器、getter、setter 略
}

我们需要找出所有活跃用户,按年龄降序排列,并只保留名字:

List<User> users = ...; // 初始化数据

List<String> names = users.stream()
    .filter(u -> u.isActive())
    .sorted(Comparator.comparingInt(User::getAge).reversed())
    .map(User::getName)
    .collect(Collectors.toList());

7.4.2 与数据库查询的对比分析

特性 Stream API SQL查询
数据源 内存集合 数据库表
执行环境 JVM内存 数据库引擎
性能 小数据快,大数据慢 支持索引优化,适合大数据
可读性 链式语法,函数式风格 声明式语法,结构清晰
适用场景 业务逻辑处理 持久层数据检索

示例对比:查询年龄大于25的用户姓名

-- SQL
SELECT name FROM users WHERE age > 25;
// Stream API
List<String> names = users.stream()
    .filter(u -> u.getAge() > 25)
    .map(User::getName)
    .collect(Collectors.toList());

两者在逻辑上非常相似,但在执行机制和性能优化上有本质区别。Stream 更适合在内存中处理业务逻辑,而 SQL 更适合在数据库层面进行高效查询。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:Java 8是Java语言的重要版本更新,带来了Lambda表达式、Stream API、新的日期时间API等重要特性,极大提升了开发效率与代码可读性。JDK 8作为开发核心,包含JRE及编译、调试工具;JRE 8则用于运行Java程序,提供JVM和基础类库支持。本文详细介绍JDK 8与JRE 8的组成、功能及其安装配置流程,适合初学者和开发者进行Java 8环境搭建与实践学习。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值