自 2014 年 Java 8 发布以来,Java 语言经历了多次重大更新,引入了许多新特性和语法改进。可是最近发现很多小伙伴还不太清楚一些新特性,这本身也没啥大不了。但是新的特性能让我们更加快速、优雅的完成工作,然后有更多的时间摸鱼,岂不是妙哉。
本文列出了Java8到Java21的部分特性变更,Java21后的特性也未列出。主要是新的LTS版本并未推出。
1. Lambda 表达式和 Stream API 的增强(Java 8+)
虽然 Lambda 表达式和 Stream API 在 Java 8 中引入,但后续版本对其进行了优化和扩展,更加的锦上添花,提供了更强大的工具。
-
Java 8 的基础:
-
Lambda 表达式:简化函数式编程,替代匿名内部类。例如:
javaList<String> list = Arrays.asList("abc", "def", "ghi"); list.forEach(s -> System.out.println(s));
-
Stream API:支持函数式数据处理,如过滤、映射:
java/** * 过滤以"a"开头的元素 */ long count = list.stream().filter(s -> s.startsWith("a")).count(); //anyMatch更合适,这边只为了map示例 boolean isThress = list.stream() .filter(s -> s.startsWith("g")) .findFirst() .map(s -> s.length() == 3) .orElse(false);
-
-
Java 9+ 改进:
-
Stream API 增强(Java 9):新增 takeWhile 和 dropWhile 方法,用于截取流:
javaStream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .takeWhile(n -> n < 4) // 遇到不满足条件的就停止 .forEach(System.out::println); // 输出:1, 2, 3 Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .dropWhile(n -> n < 4) // 跳过所有满足条件的元素,直到遇到不满足的才开始处理 .forEach(System.out::println); // 输出:4, 5, 4, 3, 2, 1
-
Optional 改进(Java 9-10):Optional 类新增 or、ifPresentOrElse 和 stream 方法,让空值的处理更加优雅便捷。
javaOptional<String> opt = Optional.of("hello"); opt.ifPresentOrElse(System.out::println, // 如果存在值,则打印 () -> System.out.println("Empty")); // 否则打印 Empty Optional<String> emptyOpt = Optional.empty(); String result = emptyOpt.or(() -> Optional.of("default")).get(); // 如果为空,提供一个替代 Optional System.out.println(result); // 输出:default // 将 Optional 转换为 Stream,方便与 Stream API 结合 emptyOpt.stream().forEach(System.out::println); // 不输出任何东西 Optional.of("stream me").stream().forEach(System.out::println); // 输出:stream me
-
-
优势 :这些改进使流处理更灵活,代码更简洁,尤其在处理复杂数据管道时。特别是
takeWhile
和dropWhile
在处理部分满足条件的数据时能显著提高效率。Optional
的增强则让空值处理更加优雅和安全。
2. 接口中的默认方法(Java 8+)
Java 8 引入了接口的 默认方法(default methods) ,允许在接口中定义带实现的方法,这在不破坏现有实现类的情况下,为接口添加新功能提供了方便。
-
语法示例:
javapublic interface Vehicle { void start(); // 抽象方法 default void stop() { // 默认方法,带有默认实现 System.out.println("Vehicle stopped"); } } public class Car implements Vehicle { public void start() { System.out.println("Car started"); } // 无需实现 stop(),使用默认实现 } // 用法 public static void main(String[] args) { Car car = new Car(); car.start(); // 输出: Car started car.stop(); // 输出: Vehicle stopped }
-
Java 9 增强:
-
接口支持 private 方法,用于复用默认方法内部的通用逻辑,而无需暴露给外部。这使得接口内部代码更整洁。
javapublic interface Vehicle { default void stop() { log("Stopping"); System.out.println("Vehicle stopped"); } // 私有方法,只能在接口内部被其他默认方法调用或静态方法调用 private void log(String message) { System.out.println("Log: " + message); } }
-
-
用途:
- 兼容性:向现有接口添加新方法,而
无需修改所有已有实现类(向后兼容)
。 - 代码复用:提供共享的默认行为,减少
实现类
中的重复代码。 - 内部封装::私有方法(Java 9)进一步支持代码复用和封装,保持简洁性。
- 兼容性:向现有接口添加新方法,而
-
优势:
- 增强接口的灵活性,特别适合框架和库的更新升级(如 Java 8 中的 Collection 接口新增 stream() 默认方法)。
- 私有方法使接口内部逻辑更清晰,避免重复代码。
-
注意事项:
-
默认方法可能引发"菱形继承问题"(多个接口提供存在默认方法)。你需要通过
InterfaceName.super.method()
语法显式指定调用哪个接口的默认方法来解决冲突。javapublic interface A { default void getHello() { System.out.println("Hello from A"); } } public interface B { default void getHello() { System.out.println("Hello from B"); } } public class MyClass implements A, B { public void getHello() { A.super.getHello(); // 显式调用 A 的默认方法 } }
-
3. var 关键字
Java 10 引入了局部变量类型推断,使用 var 关键字简化变量声明,减少了冗余代码。使用过Kotlin、TypeScript、Rust等语言的小伙伴应该不陌生,不再详细阐述概念。
-
语法示例:
javavar list = new ArrayList<String>(); // 编译器推断为 ArrayList<String> var stream = list.stream(); // 推断为 Stream<String>
-
限制:
- 仅适用于局部变量 ,且声明时必须初始化。
- 不适用于成员变量、方法参数或返回值。
- 不能用于声明多个变量 (var x = 10, y = 20, z = 30; // 这行代码也会引起编译错误!)
- 不能用于声明复合类型 (例如数组的匿名初始化
var arr = {1, 2, 3};
是不允许的,但var arr = new int[]{1, 2, 3};
是可以的,因为new int[]{...}
提供了明确的类型)。
-
优势:减少样板代码,提高可读性,尤其在复杂类型声明(如泛型)中。
4. 增强的 switch 表达式
Java 12 引入了 switch 表达式的预览特性,Java 14 正式标准化,显著改进了传统的 switch 语句,使其更加简洁和安全。
-
箭头语法(Java 12) :
switch
表达式可以像 Lambda 表达式一样使用箭头->
,直接返回一个值,避免了传统的case
块末尾忘记break
导致的问题。是不是马大哈程序猿的福音。javaString result = switch (day) { case "MONDAY", "FRIDAY" -> "工作日"; // 支持多标签 case case "SATURDAY", "SUNDAY" -> "周末休息"; default -> "Invalid";// 必须包含 default 或覆盖所有可能值 }; System.out.println(result);
-
yield 关键字(Java 13) :
javaint value = switch (month) { case "JANUARY" -> 1; case "FEBRUARY" -> 2; default -> { yield -1; } }; System.out.println(value);
-
优势:
- 代码更简洁 :消除了
break
语句的需要,减少了代码行数,减少了出错概率。 - 支持多标签
case
:一个case
可以对应多个值,提高了代码的聚合性。 - 作为表达式返回值 :可以直接将
switch
表达式的结果赋值给变量,使得代码流更流畅。 - 减少错误 :强制要求处理所有可能的
case
或提供default
分支,降低了遗漏break
或未处理情况的风险。
- 代码更简洁 :消除了
5. 文本块
Java 13 引入了文本块(Text Blocks)作为预览特性,Java 15 正式化,特别是对于包含 JSON、SQL、HTML 或其他语言代码的字符串。解决了程序猿模拟Json等字符串代码的噩梦。
-
语法示例:
javaString json = """ { "name": "John", "age": 30 } """;// 自动处理内部换行和大部分缩进 String html = """ <html> <body> <h1>Hello, Text Blocks!</h1> </body> </html> """; System.out.println(json); System.out.println(html);
-
优势:
- 避免了繁琐的转义字符 :不再需要手动添加
\n
进行换行或\
来转义内部引号。 - 自动处理缩进:自动识别移除多余的空白,使得代码更具可读性。
- 所见即所得:多行字符串的布局在代码中就是最终的输出布局,极大提高了代码的可读性和可维护性。
- 提供
stripIndent
和formatted
等方法进一步增强灵活性。
- 避免了繁琐的转义字符 :不再需要手动添加
6. Record类
Java 14 引入了 record 作为预览特性,Java 16 正式化,用于创建不可变的数据类:
-
语法示例 :
只需一行代码即可定义一个数据类。
javapublic record Person(String name, int age) {}
-
自动生成 :
编译器会自动为 Record 生成以下内容:
- 构造函数(包含所有参数)
- getter(
name()
和age()
) equals()
、hashCode()
和toString()
方法,基于所有的参数进行实现
-
用法:
javavar person = new Person("lele", 25); System.out.println(person.name()); // 通过 getter 方法访问数据:lele System.out.println(person.age()); // 通过 getter 方法访问数据:25 System.out.println(person); // 自动生成的 toString():Person[name=Alice, age=25]
-
优势:极大减少代码,适合 DTO(数据传输对象)或简单数据载体。
7. 模式匹配
模式匹配是 Java 近年来的重点改进,逐步简化了类型检查和转换:
-
instanceof 模式匹配(Java 14-16) :
结合了类型检查和强制类型转换,避免了重复的类型转换。
javaObject obj = "Hello World"; if (obj instanceof String s) { System.out.println(s.toUpperCase()); } Object num = 123; if (num instanceof Integer i && i > 100) { // 支持 && 运算符,结合条件判断 System.out.println("Large integer: " + i); }
-
switch 模式匹配(Java 17-21) :
sqlObject obj = 123; String result = switch (obj) { case String s -> "String: " + s; case Integer i -> "Integer: " + i; case null -> "Null value"; // Java 17+ 支持 null case case Double d -> "Double: " + d; default -> "Unknown type"; }; System.out.println(result);
-
优势:
- 减少显式类型转换:消除了冗余的类型转换代码。
- 代码更简洁、更安全:将类型检查和绑定变量结合在一起,减少了类型转换错误。
- 提高可读性:代码逻辑更加直观,易于理解。
8. 密封类(Sealed Classes,Java 15-17)
Java 15 引入了密封类,Java 17 正式化,用于限制类或接口的继承或实现,增加安全性。
-
语法示例 :
使用
sealed
关键字,并通过permits
列出允许的实现的子类。java// 定义一个密封接口 Shape,只允许 Circle 和 Rectangle这两个类 实现它 public sealed interface Shape permits Circle, Rectangle {} public record Circle(double radius) implements Shape {} public record Rectangle(double width, double height) implements Shape {} // 无法继承/实现 Shape,除非在 permits 列表中 public class Triangle implements Shape {} // 编译错误!
-
优势:
- 增强类型安全性 :明确指定了允许的子类,编译器可以进行更严格的检查。确保所有可能的类型都被处理(例如在模式匹配的
switch
语句中)。 - 与模式匹配结合 :密封类与
switch
表达式的模式匹配结合使用时,可以实现穷尽性检查 。如果switch
语句没有覆盖密封类的所有许可子类型,编译器会发出警告,确保你处理了所有可能的情况 - Java
- 增强类型安全性 :明确指定了允许的子类,编译器可以进行更严格的检查。确保所有可能的类型都被处理(例如在模式匹配的
java
//求取面积
double getArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
// 编译器会检查是否覆盖了所有许可子类
};
}
9. 其他实用改进
-
Java 11:
-
HTTP Client API :支持 HTTP/2 和 WebSocket 的现代 HTTP 客户端 API,告别了老旧的
HttpURLConnection
inivar client = HttpClient.newHttpClient(); var request = HttpRequest.newBuilder().uri(URI.create("https://example.com")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
String 方法 :新增了
isBlank()
、lines()
、strip()
、stripLeading()
、stripTrailing()
、indent
、repeat()
等便捷方法:javaString s = " Hello "; System.out.println(s.strip()); // "Hello"
注意:与 trim() 方法不同,strip() 方法使用的是 Unicode 标准来判断哪些字符是空白字符,因此它能更好地支持国际化文本处理。
javaString str = "Line 1\nLine 2\r\nLine 3"; str.lines().forEach(System.out::println); // 输出:Line 1 Line 2 Line 3
lines()方法用于按行分割字符串,并返回一个流(Stream),其中每个元素都是原字符串中的一个行。行分隔符可以是 \n、\r\n 或者 \r。
读取配置文件内容或日志文件,这个方法非常有用。
javaString str = " Hello, World! "; //移除字符串尾部的空白字符,并返回新的字符串,原字符串不变 String strippedStr = str.stripTrailing(); System.out.println(strippedStr); // 输出: Hello, World!
与之对应的stripLeading,不再阐述用法。
javaString str = "Hello"; String repeatedStr = str.repeat(3); System.out.println(repeatedStr); // 输出: HelloHelloHello
此方法用于将字符串重复指定的次数,并返回一个新的字符串。原始字符串不会被修改。
javaString text = "Hello\nWorld\n"; // 增加 4 个空格的缩进 String indented = text.indent(4); System.out.println(indented); // 输出: // Hello // World // 移除 2 个空格的缩进(如果存在) String textWithSpaces = " Hello\n World\n"; String unindented = textWithSpaces.indent(-2); System.out.println(unindented); // 输出: // Hello // World
注意:
- 如果输入字符串不以换行符结尾,indent() 会自动在末尾添加一个换行符。
- 如果行首的空格数少于要移除的空格数(n < 0),则只移除实际存在的空格。
- n > 0:在每行前面添加 n 个空格。
- n < 0:从每行开头移除最多 n 个空格(如果存在)。
- n = 0:不更改缩进,但会标准化换行符(移除现有行尾的换行符并在最后添加一个换行符)
-
-
Java 12 :
String.transform(Function f)
transform() 方法允许对字符串应用一个指定的转换函数(Function)。这是一个高阶方法,适用于将字符串转换为其他类型或进行自定义处理。
javaString text = "hello"; // 将字符串转为大写 Function<String, String> toUpperCase = String::toUpperCase; String result1 = text.transform(toUpperCase); System.out.println(result1); // 输出: HELLO // 将字符串转为长度(整数) Function<String, Integer> toLength = String::length; Integer result2 = text.transform(toLength); System.out.println(result2); // 输出: 5 // 自定义转换:添加前缀 Function<String, String> addPrefix = s -> "prefix_" + s; String result3 = text.transform(addPrefix); System.out.println(result3); // 输出: prefix_hello
- 返回值:Function 处理后的结果,类型为 T。
-
Java 17:
- 随机数生成器改进 :新的
RandomGenerator
接口及其工厂类RandomGeneratorFactory
,统一随机数生成。 - 增强的伪随机数生成器(PRNG)。
javaimport java.util.random.RandomGenerator; import java.util.random.RandomGeneratorFactory; // 获取默认推荐的随机数生成器,生成0-100的整数 RandomGenerator defaultGenerator = RandomGenerator.getDefault(); System.out.println("默认随机数: " + defaultGenerator.nextInt(100)); // 获取特定算法的随机数生成器 (例如:L64X128MixRandom) RandomGenerator l64Generator = RandomGeneratorFactory.of("L64X128MixRandom").create(); System.out.println("L64X128MixRandom随机数: " + l64Generator.nextInt(100)); // 获取加密安全的随机数生成器 (例如:SecureRandom) RandomGenerator secureGenerator = RandomGeneratorFactory.of("SecureRandom").create(); System.out.println("SecureRandom随机数: " + secureGenerator.nextInt(100));
- 随机数生成器改进 :新的
-
Java 21:
-
虚拟线程(Virtual Threads) :轻量级线程,极大提高并发性能:
javaimport java.util.concurrent.Executors; // 使用虚拟线程执行器,每个任务都运行在一个轻量级虚拟线程中 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 10_000; i++) { // 轻松创建大量并发任务 final int taskId = i; executor.submit(() -> { // 模拟耗时操作,如网络请求或数据库查询 Thread.sleep(10); System.out.println("Task " + taskId + " running in virtual thread: " + Thread.currentThread()); }); } } // executor.close() 会等待所有任务完成 System.out.println("All tasks submitted.");
-
序列集合(Sequenced Collections) :引入
SequencedCollection
、SequencedSet
和SequencedMap
接口,统一了具有定义顺序的集合类型,并提供了访问首尾元素和反向视图的方法,让操作更加直观。
-
总结
Java 语言在不断进步,每个版本都带来了旨在提高开发者效率、改善代码质量和提升性能的新特性。
- 代码更加简洁:更少的代码完成相同工作。
- 提高开发效率:减少不必要的编码工作,把更多精力放在摸鱼上。
- 提高性能:底层运行时优化和新 API 将让你的应用跑得更快。
所以,是时候使用新的 Java 特性,你的"摸鱼"时间也会增加。