Java 20没有发布重要的新特性,本文以Java21版本为主。
Java 21 是新的LTS版本,其中发布了众多新的特性。
字符串模板(预览版)
使用官方的STR、FMT
支持通过STR
、FMT
来实现字符串拼接,可以自定义模板处理器组织字符串输出形式。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import static java.util.FormatProcessor.FMT;
/**
* @author imyzt
* @date 2023/12/19
* @description 模板字符串
*/
public class TemplateString {
public static void main(String[] args) {
String str = "world";
String result = STR."hello \{str}";
System.out.println(result);
System.out.println(STR);
String name = "yzt";
String[] blogAddress = {"imyzt.top", "blog.imyzt.top"};
String text = FMT."""
My name is \{name}
My blog address is \{blogAddress[0].toUpperCase()}, \{blogAddress[1].toLowerCase()}""";
System.out.println(text);
System.out.println(STR."\{Math.random()}");
System.out.println(STR."\{Integer.MAX_VALUE}");
System.out.println(STR."\{index++}, \{++index}");
//hello world
//java.lang.StringTemplate$$Lambda/0x00000001260457f0@33c7353a
//My name is yzt
//My blog address is IMYZT.TOP, blog.imyzt.top
//0.9361799484353136
//2147483647
//现在的时间是: 2023-12-20
//0, 2
}
}
自定义StringTemplate
import java.util.Iterator;
/**
* @author imyzt
* @date 2023/12/20
* @description 自定义StringTemplate
*/
public class CustomTemplate {
public static void main(String[] args) {
var INTER = StringTemplate.Processor.of((StringTemplate st) -> {
StringBuilder sb = new StringBuilder();
Iterator<String> fragments = st.fragments().iterator();
for (Object value : st.values()) {
sb.append(fragments.next());
sb.append(value);
}
sb.append(fragments.next());
return sb.toString();
});
String name = "yzt";
int index = 0;
String text = INTER."""
{
"name":\{name},
"index":\{++index}
}
""";
System.out.println(text);
//{
//"name":yzt,
//"index":1
//}
}
}
Record Patterns
记录模式更强大了,支持直接在表达式中创建和使用。还支持嵌套
/**
* @author imyzt
* @date 2023/12/19
* @description 记录模式
*/
public class RecordPatterns {
public static void main(String[] args) {
Object p = new Point(1, 2);
if (p instanceof Point(int x, int y)) {
System.out.println(x + y);
}
Object w = new Window(new Point(1, 2), 3);
if (w instanceof Window(Point(int x, int y), int z)) {
System.out.println(x + y + z);
}
}
}
record Point(int x, int y) {}
record Window(Point p, int z) {}
ScopedValue
ScopedValue
是一个隐藏的方法参数,只有方法可以访问ScopedValue
,它可以让两个方法传递参数时无需声明形参(对于Web项目中传递用户信息是非常场景的操作)
ThreadLocal的问题
通常对于上述场景,都会采用ThreadLocal
来解决,但是由于ThreadLocal
在设计上的瑕疵,导致一直有以下问题:
- 内存泄露,在使用完
ThreadLocal
之后,若没有调用remove
方法,会出现内存泄漏。 - 增加开销,在具有继承关系的线程中,子线程需要为父线程中
ThreadLocal
里面的数据分配内存 - 不是不可变对象,在方法中可以随意调用
set
方法篡改。
随着虚拟线程的到来,内存泄漏问题不用担心了,因为虚拟线程会很快的终止,此时会自动删除ThreadLocal
中的数据,这样就不用调用remove
方法了。
但是虚拟线程的数量是非常多的,假如有上百万个虚拟线程都要拷贝一份ThreadLocal
中的变量(问题2),内存将会被严重的浪费掉。
示例
示例代码中通过模拟一个“送礼物”的场景,来演示ScopedValue
。
- 在
giveGift
方法中,将礼物送出,是500元 - 在
receiveMiddleMan
方法模拟中间人抽成的场景,抽成后送出仅 200元 - 在
receiveGift
方法中 ,对ScopedValue
进行获取,得到的就是200元
但整个过程中,中间人 receiveMiddleMan
在送出前后获取到的信息都是 500元,这正是 Scope
的含义,修改只在本作用域(方法)中生效。
/**
* @author imyzt
* @date 2023/12/20
* @description ScopedValue
*/
public class ScopedValueTest {
private static final ScopedValue<String> GIFT = ScopedValue.newInstance();
public static void main(String[] args) {
ScopedValueTest test = new ScopedValueTest();
test.giveGift();
//中间人开始: 500
//收礼物: 200
//中间人结束: 500
}
private void giveGift() {
ScopedValue.where(GIFT, "500").run(this::receiveMiddleMan);
}
private void receiveMiddleMan() {
System.out.println(STR."中间人开始: \{GIFT.get()}");
ScopedValue.where(GIFT, "200").run(this::receiveGift);
System.out.println(STR."中间人结束: \{GIFT.get()}");
}
private void receiveGift() {
System.out.println(STR."收礼物: \{GIFT.get()}");
}
}
Switch 又升级了,支持when关键字了
switch 可谓是升级升级再升级,我还是在用 if/else 啊。
从12开始,一路升级。
- 支持
->
表达式(Java 12) - 支持返回值(Java 12)
case
支持单行写多个条件(Java 12)- 新增
yield
关键字返回switch
的值(Java 12) - 引入模式匹配(Java 15)
- 这回又新增了一个
when
关键字(都快改成SQL了) (Java 21)
/**
* @author imyzt
* @date 2023/12/20
* @description Switch 又升级了~, 这次支持when关键字了
*/
public class SwitchFuture {
void main() {
var str = "yes";
var result = switch (str) {
case null -> "空对象";
case String s
when "yes".equals(s) -> {
System.out.println("确定");
yield "字符串的Yes";
}
case String s
when "no".equals(s) -> {
System.out.println("取消");
yield "字符串的No";
}
default -> "default";
};
System.out.println(result);
//确定
//字符串的Yes
}
}
简化了main方法(注意看上一个章节的main方法)
- Class声明和强制的public访问修饰符是必须的。当用在外部组件定义良好的接口封装代码单元时,它们很有用。但在这个小例子中,它们毫无意义。
- String[]参数主要用于将代码与外部组件(在本例中为操作系统的shell,接收命令传入的参数)连接。它在这里很神秘且无用,尤其是它从未被使用过。
- static修饰符是Java类和对象模型的一部分。对于新手来说,这不仅是神秘的,而且是有害的:要添加更多可以调用和使用的方法或字段,学生必须要么将它们全部声明(传播一种既不常见也不是好习惯的用法),或者就要面对是否有static修饰的区别问题,并学习如何实例化对象。
未命名模式和变量(Go:和我有点像)
/**
* @author imyzt
* @date 2023/12/21
* @description 像golang学习, 支持不使用的变量不命名了
*/
public class UnnamedVariable {
void main() {
List<String> list = new ArrayList<>();
list.add("first");
list.add("last");
try {
System.out.println(STR."first -> \{list.getFirst()}");
System.out.println(STR."last -> \{list.getLast()}");
System.out.println("try");
} catch (Exception _) {
System.out.println("异常了, 但是我没有 Exception");
}
}
}
有序集合
Sequenced Collections
Sequenced Collections引入了三个新接口:
- SequencedCollection
- SequencedMap
- SequencedSet
接口都附带了一些新方法,可以提高操作集合的效率。比如:java.util.SequencedCollection#reversed
等等。
集合补充了获取收尾元素的方法(hutool:当我不存在?)
List<String> list = new ArrayList<>();
list.add("first");
list.add("last");
System.out.println(STR."first -> \{list.getFirst()}");
System.out.println(STR."last -> \{list.getLast()}");
加了一些工具类
- StringBuilder中的repeat方法
- 补充
java.lang.Character#isEmoji
- 等等
/**
* @author imyzt
* @date 2023/12/21
* @description 加了些新方法
*/
public class SomethingExample {
void main() {
System.out.println(STR."repeat => \{new StringBuffer().repeat("*", 10)}");
//repeat => **********
var happy = "你开心吗? 😄";
System.out.println(STR."isEmoji => \{happy.codePoints().anyMatch(Character::isEmoji)}");
//isEmoji => true
}
}