你好,我是看山。
本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。
概述
从 2017 年开始,Java 版本更新遵循每六个月发布一次的节奏,LTS版本则每两年发布一次,以快速验证新特性,推动 Java 的发展。
提升幸福感的特性
局部变量类型推断(Java 10)
// 以前的写法
Map<String, List<ProcSequenceFlow>> sourceMap = sequenceFlowList.stream()
.collect(Collectors.groupingBy(ProcSequenceFlow::getSourceRef));
// 现在的写法
var sourceMap2 = sequenceFlowList.stream()
.collect(Collectors.groupingBy(ProcSequenceFlow::getSourceRef));
Switch表达式(Java 14)
我们以“判断是否工作日”的例子展示一下,在Java14之前:
@Test
void testSwitch() {
final DayOfWeek day = DayOfWeek.from(LocalDate.now());
String typeOfDay = "";
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
typeOfDay = "Working Day";
break;
case SATURDAY:
case SUNDAY:
typeOfDay = "Rest Day";
break;
}
Assertions.assertFalse(typeOfDay.isEmpty());
}
在Java14中:
@Test
void testSwitchExpression() {
final DayOfWeek day = DayOfWeek.SATURDAY;
final String typeOfDay = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> {
System.out.println("Working Day: " + day);
yield "Working Day";
}
case SATURDAY, SUNDAY -> "Day Off";
};
Assertions.assertEquals("Day Off", typeOfDay);
}
注意看,例子中我们使用了两种写法,一种是通过yield
关键字表示返回结果,一种是在->
之后直接返回。
文本块(Java 15)
直接上代码
@Test
void testTextBlock() {
final String singleLine = "你好,我是看山,公众号「看山的小屋」。这行没有换行,而且我的后面多了一个空格 \n 这次换行了";
final String textBlockSingleLine = """
你好,我是看山,公众号「看山的小屋」。\
这行没有换行,而且我的后面多了一个空格\s
这次换行了""";
Assertions.assertEquals(singleLine, textBlockSingleLine);
}
字符串模板(预览)
字符串模板通过将文本和嵌入式表达式结合在一起,使得Java程序能够以一种更加直观和安全的方式构建字符串。
与传统的字符串拼接(使用+操作符)、StringBuilder
或String.format
等方法相比,字符串模板提供了一种更加清晰和安全的字符串构建方式。
特别是当字符串需要从用户提供的值构建并传递给其他系统时(例如,构建数据库查询),使用字符串模板可以有效地验证和转换模板及其嵌入表达式的值,从而提高Java程序的安全性。
让我们通过代码看一下这个特性的魅力:
public static void main(String[] args) {
// 拼装变量
String name = "看山";
String info = STR. "My name is \{ name }" ;
assert info.equals("My name is 看山");
// 拼装变量
String firstName = "Howard";
String lastName = "Liu";
String fullName = STR. "\{ firstName } \{ lastName }" ;
assert fullName.equals("Howard Liu");
String sortName = STR. "\{ lastName }, \{ firstName }" ;
assert sortName.equals("Liu, Howard");
// 模板中调用方法
String s2 = STR. "You have a \{ getOfferType() } waiting for you!" ;
assert s2.equals("You have a gift waiting for you!");
Request req = new Request("2017-07-19", "09:15", "https://www.howardliu.cn");
// 模板中引用对象属性
String s3 = STR. "Access at \{ req.date } \{ req.time } from \{ req.address }" ;
assert s3.equals("Access at 2017-07-19 09:15 from https://www.howardliu.cn");
LocalTime now = LocalTime.now();
String markTime = DateTimeFormatter
.ofPattern("HH:mm:ss")
.format(now);
// 模板中调用方法
String time = STR. "The time is \{
// The java.time.format package is very useful
DateTimeFormatter
.ofPattern("HH:mm:ss")
.format(now)
} right now" ;
assert time.equals("The time is " + markTime + " right now");
// 模板嵌套模板
String[] fruit = {"apples", "oranges", "peaches"};
String s4 = STR. "\{ fruit[0] }, \{
STR. "\{ fruit[1] }, \{ fruit[2] }"
}" ;
assert s4.equals("apples, oranges, peaches");
// 模板与文本块结合
String title = "My Web Page";
String text = "Hello, world";
String html = STR. """
<html>
<head>
<title>\{ title }</title>
</head>
<body>
<p>\{ text }</p>
</body>
</html>
""" ;
assert html.equals("""
<html>
<head>
<title>My Web Page</title>
</head>
<body>
<p>Hello, world</p>
</body>
</html>
""");
// 带格式化的字符串模板
record Rectangle(String name, double width, double height) {
double area() {
return width * height;
}
}
Rectangle[] zone = new Rectangle[] {
new Rectangle("Alfa", 17.8, 31.4),
new Rectangle("Bravo", 9.6, 12.4),
new Rectangle("Charlie", 7.1, 11.23),
};
String table = FMT. """
Description Width Height Area
%-12s\{ zone[0].name } %7.2f\{ zone[0].width } %7.2f\{ zone[0].height } %7.2f\{ zone[0].area() }
%-12s\{ zone[1].name } %7.2f\{ zone[1].width } %7.2f\{ zone[1].height } %7.2f\{ zone[1].area() }
%-12s\{ zone[2].name } %7.2f\{ zone[2].width } %7.2f\{ zone[2].height } %7.2f\{ zone[2].area() }
\{ " ".repeat(28) } Total %7.2f\{ zone[0].area() + zone[1].area() + zone[2].area() }
""";
assert table.equals("""
Description Width Height Area
Alfa 17.80 31.40 558.92
Bravo 9.60 12.40 119.04
Charlie 7.10 11.23 79.73
Total 757.69
""");
}
public static String getOfferType() {
return "gift";
}
record Request(String date, String time, String address) {
}
这个功能当前是第一次预览,在Java22第二次预览,Java23的8.12版本中还没有展示字符串模板的第三次预览(JEP 465: String Templates),还不能确定什么时候可以正式用上。
模式匹配
instanceof模式匹配(Java 16)
instanceof
主要用来检查对象类型,作为类型强转前的安全检查。
比如:
@Test
void test() {
final Object obj1 = "Hello, World!";
int result = 0;
if (obj1 instanceof String) {
String str = (String) obj1;
result = str.length();
} else if (obj1 instanceof Number) {
Number num = (Number) obj1;
result = num.intValue();
}
Assertions.assertEquals(13, result);
}
可以看到,我们每次判断类型之后,需要声明一个判断类型的变量,然后将判断参数强制转换类型,赋值给新声明的变量。
这种写法显得繁琐且多余。
instanceof
改进后:
@Test
void test1() {
final Object obj1 = "Hello, World!";
int result = 0;
if (obj1 instanceof String str) {
result = str.length();
} else if (obj1 instanceof Number num) {
result = num.intValue();
}
Assertions.assertEquals(13, result);
}
不仅如此,instanceof
模式匹配的作用域还可以扩展。在if
条件判断中,我们都知道&&
与判断是会执行所有的表达式,所以使用instanceof
模式匹配定义的局部变量继续判断。
比如:
if (obj1 instanceof String str && str.length() > 20) {
result = str.length();
}
与原来的写法对比,新的写法代码更加简洁、可读性更高,能够提出很多冗余繁琐的代码,非常实用的一个特性。
switch模式匹配(Java 21)
switch模式匹配是一个非常赞的功能,可以在选择器表达式使用基础判断和任意引用类型,包括instanceof操作符。这意味着可以更灵活地使用对象、数组、列表等复杂数据结构作为switch语句的基础,从而简化代码并提高可读性。
switch模式匹配允许在switch语句中使用模式来测试表达式,每个模式都有特定的动作,从而可以简洁、安全地表达复杂的数据导向查询。
通过代码看下switch模式匹配的魅力;
static String formatValue(Object obj) {
return switch (obj) {
case null -> "null";
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
case Person(String name, String address) -> String.format("Person %s %s", name, address);
default -> obj.toString();
};
}
public record Person(String name, String address) {}
public static void main(String[] args) {
System.out.println(formatValue(10));
System.out.println(formatValue(20L));
System.out.println(formatValue(3.14));
System.out.println(formatValue("Hello"));
System.out.println(formatValue(null));
System.out.println(formatValue(new Person("Howard", "Beijing")));
}
// 运行结果
// int 10
// long 20
// double 3.140000
// String Hello
// null
// Person Howard Beijing
Record 模式(Java 21)
Record是Java16正式发布的基础类型,提供不可变对象的简单实现(其实就是Java Bean,但是省略一堆的getter、setter、hashcode、equals、toString等方法)。
Record模式,主要是使Record类型可以直接在instanceof和switch模式匹配中使用。
我们一起看个示例,比如有下面几个基础元素:
// 颜色
enum Color { RED, GREEN, BLUE}
// 点
record Point(int x, int y) {}
// 带颜色的点
record ColoredPoint(Point p, Color color) {}
// 正方形
record Square(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
我们分别通过instanceof模式匹配和switch模式匹配判断输入参数的类型,打印不同的格式:
private static void instancePatternsAndPrint(Object o) {
if (o instanceof Square(ColoredPoint upperLeft, ColoredPoint lowerRight)) {
System.out.println("Square类型:" + upperLeft + " " + lowerRight);
} else if (o instanceof ColoredPoint(Point(int x, int y), Color color)) {
System.out.println("ColoredPoint类型:" + x + " " + y + " " + color);
} else if (o instanceof Point p) {
System.out.println("Point类型:" + p);
}
}
private static void switchPatternsAndPrint(Object o) {
switch (o) {
case Square(ColoredPoint upperLeft, ColoredPoint lowerRight) -> {
System.out.println("Square类型:" + upperLeft + " " + lowerRight);
}
case ColoredPoint(Point(int x, int y), Color color) -> {
System.out.println("ColoredPoint类型:" + x + " " + y + " " + color);
}
case Point p -> {
System.out.println("Point类型:" + p);
}
default -> throw new IllegalStateException("Unexpected value: " + o);
}
}
我们通过main方法执行下:
public static void main(String[] args) {
var p = new Point(1, 2);
var cp1 = new ColoredPoint(p, Color.RED);
var cp2 = new ColoredPoint(p, Color.GREEN);
var square = new Square(cp1, cp2);
instancePatternsAndPrint(square);
instancePatternsAndPrint(cp1);
instancePatternsAndPrint(p);
switchPatternsAndPrint(square);
switchPatternsAndPrint(cp1);
switchPatternsAndPrint(p);
}
// 结果是:
//
// Square类型:ColoredPoint[p=Point[x=1, y=2], color=RED] ColoredPoint[p=Point[x=1, y=2], color=GREEN]
// ColoredPoint类型:1 2 RED
// Point类型:Point[x=1, y=2]
//
// Square类型:ColoredPoint[p=Point[x=1, y=2], color=RED] ColoredPoint[p=Point[x=1, y=2], color=GREEN]
// ColoredPoint类型:1 2 RED
// Point类型:Point[x=1, y=2]
是不是很简洁,就像Java8提供的Lambda表达式一样,很丝滑。
灵活的构造函数主体(预览特性)
我们都知道,在子类的构造函数中,比如通过super(……)
调用父类,在super
之前是不允许有其他语句的。
大部分的时候这种限制都没问题,但是有时候不太灵活。如果想在super
之前加上一些子类特有逻辑,比如想统计下子类构造耗时,就得重写一遍父类的实现。
除了有损灵活性,这种重写的做法也会造成父子类之间的关系变得奇怪。假设父类是SDK中的一个类,SDK升级时在父类构造函数增加了一些逻辑,我们项目中是无法继承这些逻辑的,某次需要升级SDK(比如低版本有安全风险),验证不完整的情况下,就很容易出现bug。
该特性的目标是提高构造函数的可读性和可预测性,同时保持构造函数调用的自上而下的规则。通过允许在显式调用 super()
或 this()
前初始化字段,从而实现更灵活的构造函数主体。这一变化使得代码更具表现力。
我们看下示例代码:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0) {
throw new IllegalArgumentException("non-positive value");
}
super(value);
}
}
文末总结
想要了解各版本的详细特性,可以从从小工到专家的 Java 进阶之旅 系列专栏中查看。
青山不改,绿水长流,我们下次见。
推荐阅读
- 从小工到专家的 Java 进阶之旅
- 一文掌握 Java8 Stream 中 Collectors 的 24 个操作
- 一文掌握 Java8 的 Optional 的 6 种操作
- 使用 Lambda 表达式实现超强的排序功能
- Java8 的时间库(1):介绍 Java8 中的时间类及常用 API
- Java8 的时间库(2):Date 与 LocalDate 或 LocalDateTime 互相转换
- Java8 的时间库(3):开始使用 Java8 中的时间类
- Java8 的时间库(4):检查日期字符串是否合法
- Java8 的新特性
- Java9 的新特性
- Java10 的新特性
- Java11 中基于嵌套关系的访问控制优化
- Java11 的新特性
- Java12 的新特性
- Java13 的新特性
- Java14 的新特性
- Java15 的新特性
- Java16 的新特性
- Java17 的新特性
- Java18 的新特性
- Java19 的新特性
- Java20 的新特性
- Java21 的新特性
- Java22 的新特性
- Java23 的新特性
- Java24 的新特性
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。
个人主页:https://www.howardliu.cn
个人博文:从Java8到Java23值得期待的x个特性(2):提升幸福感的特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:从Java8到Java23值得期待的x个特性(2):提升幸福感的特性