Java9 新特性
接口私有方法
public interface MyInterface {
//定义私有方法
private void m1() {
System.out.println("123");
}
//default中调用
default void m2() {
m1();
}
}
可以在接口中声明private修饰的方法了,其实就是为了让default方法调用,当然接口外部是无法访问私有方法的
改进的try with resource
/*
改进了try-with-resources语句,可以在try外进行初始化,在括号内填写引用名,即可实现资源自动关闭
*/
public class TryWithResource {
public static void main(String[] args) throws FileNotFoundException {
//jdk8以前
try (FileInputStream fileInputStream = new FileInputStream("");
FileOutputStream fileOutputStream = new FileOutputStream("")) {
} catch (IOException e) {
e.printStackTrace();
}
//jdk9
FileInputStream fis = new FileInputStream("");
FileOutputStream fos = new FileOutputStream("");
//多资源用分号隔开
try (fis; fos) {
} catch (IOException e) {
e.printStackTrace();
}
}
}
不能使用下划线命名变量
后面的版本中会将下划线作为关键字来使用
字符串的变化
Java 9 对字符串进行了重要的内部优化和改进。主要变化有以下几点:
-
Compact Strings (压缩字符串):
- 在 Java 9 之前,
String
类的底层实现是基于char[]
,每个字符占用 2 个字节(因为 Java 中的char
是基于 UTF-16 编码)。 - 在 Java 9 中,字符串的内部实现改为了基于
byte[]
而不是char[]
。同时引入了一个新的字段coder
来表示字符的编码类型(例如 LATIN1 或 UTF-16)。如果字符串只包含单字节的 Latin-1 字符集(ASCII 的扩展),则每个字符只占用 1 个字节;否则,使用 UTF-16 编码,每个字符占用 2 个字节。 - 这样做的好处是可以减少内存占用,尤其是对于大量包含 ASCII 字符的字符串。
- 在 Java 9 之前,
-
字符串拼接的优化:
- Java 9 通过引入
invokedynamic
指令来优化字符串拼接的性能。在之前版本中,字符串拼接通常通过StringBuilder
实现,而在 Java 9 中,通过invokedynamic
调用,在运行时生成更加高效的拼接代码。 - 这使得简单的字符串拼接操作更加高效,减少了不必要的中间对象创建。
在大多数情况下,直接使用 + 拼接字符串就已经非常高效,不再需要像以前那样担心性能问题。不过如果有非常复杂的拼接需求(例如大数据处理中的大量字符串拼接),仍然可以使用 StringBuilder 来更精确地控制内存和性能。
- Java 9 通过引入
-
Immutable collections with String:
- Java 9 提供了几个创建不可变集合的工厂方法,例如
List.of()
、Set.of()
和Map.of()
,这些方法对于包含字符串的集合提供了更方便的操作,减少了冗余代码,提升了代码的可读性和安全性。
- Java 9 提供了几个创建不可变集合的工厂方法,例如
通过这些优化,Java 9 提升了字符串操作的内存效率和性能,特别是在处理大量字符串或频繁拼接操作时,能显著减少内存占用并提升运行时的性能。
模块化系统
Java 9 引入了一个非常重要的特性——模块化系统,也称为 Java Platform Module System (JPMS),这是自 Java 以来最大的架构变动之一。这个模块化系统通过 Project Jigsaw 实现,目的是解决 Java 应用程序随着规模增大所带来的复杂性和性能问题。
以下是 Java 9 模块化系统的关键点:
1. 模块化系统的引入
在 Java 9 之前,Java 程序是基于包(package)和 JAR 文件来组织的,随着项目规模增大,类之间的依赖和管理变得越来越复杂。Java 9 的模块化系统允许将代码组织成模块,使得系统可以更好地进行依赖管理、封装、版本控制和性能优化。
- 模块(Module):一个模块是一个自包含的代码和资源集合,通常由多个包组成。
- 模块描述符文件(module-info.java):每个模块通过一个
module-info.java
文件来定义,明确声明模块的依赖、暴露的 API 等信息。示例如下:
module com.example.module {
requires java.base;
exports com.example.api;
}
requires
关键字声明了当前模块的依赖项。exports
关键字声明了当前模块暴露给其他模块使用的包。
2. 模块化的优点
Java 模块化带来了以下几个显著的优势:
2.1. 更好的封装性
- 在 Java 9 之前,包中的类只要是
public
的,就可以被任何其他包访问,模块化系统通过exports
关键字控制哪些包对外可见,哪些包是模块的内部实现,不对外暴露。这提升了代码的封装性和安全性。
2.2. 模块化JDK
- Java 9 对整个 JDK 进行了模块化,JDK 被拆分成了更小的模块(如
java.base
,java.sql
,java.xml
等)。这使得你可以根据需求选择只使用某些模块,而不必加载整个 JDK。这样既提高了应用的启动速度,又减少了资源占用。
2.3. 依赖管理
- 模块化系统明确了模块之间的依赖关系,解决了传统 Java 应用中的 类路径地狱(Classpath Hell) 问题。因为以前的类路径是一个线性结构,容易引发冲突和重复依赖,而模块化系统通过
requires
使得依赖关系更加清晰。
2.4. 编译时和运行时的可靠性
- 模块化使得在编译时就可以检测出缺失的依赖或不正确的模块组合,避免了运行时才发现这些问题。模块化系统引入的强类型依赖关系使得运行时错误更少。
2.5. 性能优化
- 模块化可以帮助 JVM 更好地进行性能优化,尤其是启动时间和内存占用。因为模块系统允许 JVM 只加载实际需要的模块,而不是整个 JDK,这减少了应用程序的启动开销。
3. 模块化的基本结构
- 模块:模块是一个最小的可部署单元,通常由多个包组成,并且可能有依赖关系。
- 模块路径(Module Path):模块化应用程序使用模块路径而不是类路径,模块路径定义了应用程序依赖的模块所在的目录或 JAR 文件。
4. 模块化对现有项目的影响
- 对于已有的 Java 项目,模块化是向后兼容的。即使你不为项目创建
module-info.java
文件,你的代码也可以在 Java 9 中正常运行,因为所有没有模块描述符的代码会被归类为所谓的 未命名模块(unnamed module)。
5. 模块化系统的挑战
- 复杂性增加:虽然模块化带来了很多好处,但对于大型现有项目来说,迁移到模块化系统需要较多的工作,特别是处理依赖和重构模块边界。
- 第三方库支持:一些第三方库可能还没有完全支持模块化系统,导致迁移时可能会遇到依赖问题。
6. 示例:模块化应用程序
假设我们有一个简单的模块化应用程序,分为两个模块:一个模块提供 API,另一个模块使用它。
模块1:com.example.api
// module-info.java
module com.example.api {
exports com.example.api;
}
package com.example.api;
public class HelloService {
public String sayHello() {
return "Hello, Java 9!";
}
}
模块2:com.example.app
// module-info.java
module com.example.app {
requires com.example.api;
}
package com.example.app;
import com.example.api.HelloService;
public class MainApp {
public static void main(String[] args) {
HelloService helloService = new HelloService();
System.out.println(helloService.sayHello());
}
}
编译和运行:
# 编译模块
javac -d mods/com.example.api src/com.example.api/module-info.java src/com.example.api/com/example/api/HelloService.java
javac -d mods/com.example.app --module-path mods src/com.example.app/module-info.java src/com.example.app/com/example/app/MainApp.java
# 运行模块
java --module-path mods -m com.example.app/com.example.app.MainApp
jshell
Java 9 引入了一个全新的工具 JShell,这是 Java 自带的一个交互式 REPL(Read-Eval-Print Loop) 环境。JShell 允许开发者在不需要编写完整类或方法的情况下,快速执行 Java 代码片段并查看结果。这对学习 Java、快速测试代码片段以及实验新功能非常有用。
JShell 的特点和优点
-
交互式编程:
- JShell 提供了交互式编程环境,开发者可以输入 Java 语句、表达式、变量声明等并立即看到结果,而不需要编写完整的类或
main
方法。
例如:
jshell> int x = 10; x ==> 10 jshell> x + 5; $2 ==> 15
- JShell 提供了交互式编程环境,开发者可以输入 Java 语句、表达式、变量声明等并立即看到结果,而不需要编写完整的类或
-
快速验证代码:
- JShell 非常适合用来验证小段代码的逻辑。你可以快速测试算法、表达式,或者调用 Java API 而不需要编译整个程序。JShell 是即时反馈的。
-
即时反馈与实验性开发:
- 在写代码时,尤其是调试或学习某个库的 API 时,JShell 能够让你迅速进行实验。例如:
jshell> System.out.println("Hello, JShell!"); Hello, JShell!
-
部分代码的执行:
- 在常规 Java 开发中,必须编写完整的类、方法等,但在 JShell 中,你可以执行单独的语句或表达式,例如:
jshell> int a = 5; jshell> int b = 10; jshell> a + b; $3 ==> 15
-
历史命令与自动补全:
- JShell 支持命令历史功能,可以通过
up
和down
键浏览之前的命令,也可以使用TAB
键进行代码的自动补全,这大大提高了效率。
- JShell 支持命令历史功能,可以通过
-
保存与加载代码片段:
-
在 JShell 中你可以保存当前的代码片段到文件,也可以从文件加载代码片段进行执行。例如:
-
保存代码:
/save myCode.jsh
-
加载代码:
/open myCode.jsh
-
-
内置命令:
- JShell 提供了一些内置命令,以
/
开头。例如:/list
列出所有定义的代码片段。/vars
显示当前定义的变量。/methods
显示当前定义的方法。/reset
重置所有状态,清除当前会话中的定义。/exit
退出 JShell。
- JShell 提供了一些内置命令,以
-
代码补全和类型推断:
- JShell 支持基于当前上下文的代码补全功能,输入代码片段时按下
Tab
键可以获得可能的补全选项。同时,JShell 也支持基本的类型推断,在不显式声明变量类型时,JShell 会自动推断类型,例如:
jshell> var name = "Java 9"; name ==> "Java 9"
- JShell 支持基于当前上下文的代码补全功能,输入代码片段时按下
JShell 的使用场景
-
快速测试小代码片段:
- 你可以通过 JShell 快速测试单个表达式或 Java API,进行实验性开发。例如:
jshell> Math.max(10, 20); $1 ==> 20
-
学习和教学:
- 对于初学者来说,JShell 是学习 Java 的极佳工具。它允许学生在不编写复杂程序的情况下,逐步学习 Java 的语法和库,并实时看到结果。
-
原型开发:
- 当你要开发新功能时,可以先通过 JShell 测试各种逻辑和算法,避免写大量的模板代码,从而快速验证想法。
-
调试和性能分析:
- JShell 可用来快速测试和调试某个库或框架中的某个方法,验证输入和输出的正确性。
JShell 常用命令示例
-
查看帮助:
/help
-
查看变量:
/vars
-
查看方法:
/methods
-
查看已加载的类和代码片段:
/list
-
重置环境:
/reset
-
退出 JShell:
/exit
示例:使用 JShell 进行快速测试
$ jshell
| Welcome to JShell -- Version 9
| For an introduction type: /help intro
jshell> int a = 5;
a ==> 5
jshell> int b = 10;
b ==> 10
jshell> a + b;
$3 ==> 15
jshell> for (int i = 0; i < 5; i++) { System.out.println(i); }
0
1
2
3
4