我的编程空间,编程开发者的网络收藏夹
学习永远不晚

要不要升级?Java 21强大的新特性,代码量减半

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

要不要升级?Java 21强大的新特性,代码量减半

1.1 instanceof类型模式

Object obj = "Pack" ;
// Java 16之前
if (obj instanceof String) {
    String s = (String) obj ;
    System.out.println("强转为String") ;
}
// 自Java 16起
if (obj instanceof String s) {
    System.out.println("简便多了") ;
}

在上面的代码中从java16开始,运行时obj的值是String的实例,则obj与类型模式String s匹配。如果模式匹配,则表达式的实例为true,并且模式变量s初始化为obj转换为String的值,然后可以在包含的代码块中使用该值。

1.2 模式匹配与Records

Records (JEP 395)是数据的透明载体。接收record类实例的代码通常将使用内置的组件访问器方法提取数据,称为组件。例如,我们可以使用类型模式来测试值是否是record类Point的实例,如果是,则从值中提取x和y组件:

// 自Java 16起
public record Point(int x, int y) {
}
public static void main(String[] args) {
  Object obj = new Point(10, 20);
  if (obj instanceof Point p) {
    int x = p.x();
    int y = p.y();
    System.out.println(x + y);
  }
}

上面的代码看着与1.1中介绍的没撒区别就是类型模式,在上面的代码中我们仅仅是访问了record类x与y的方法,如果是这样我们还可以像下面这样操作:

Object obj = new Point(10, 20) ;
// 自java 21起
if (obj instanceof Point(int x, int y)) {
  System.out.println(x + y) ;
}

这里的Point(int x, int y) 是一个record模式。它将提取组件的局部变量声明移至模式本身,并在值与模式匹配时通过调用访问器方法初始化这些变量。

1.3 嵌套record模式

有如下定义

public record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}

如果要提取左上角点的颜色,我们可以这样写:

Object r = new Rectangle(
    new ColoredPoint(new Point(0, 0), Color.RED), 
    new ColoredPoint(new Point(100, 100), Color.BLUE)
  ) ;
// 从java 21起  
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
  System.out.printf("%s, %s%n", ul, lr) ;
}

输出结果

ColoredPoint[p=Point[x=0, y=0], c=RED], ColoredPoint[p=Point[x=100, y=100], c=BLUE]

如果你希望访问具体的颜色值,record模式还支持嵌套,如下示例:

// 从java 21起
if (r instanceof Rectangle(
    ColoredPoint(Point(int x, int y), Color c1), 
    ColoredPoint lr
  )
) {
  System.out.printf("x = %d, y = %d%n", x, y) ;
}

1.4 嵌套模式无法匹配情况

在下面这情况下是无法进行匹配的

public record Pair(Object x, Object y) {}
Pair p = new Pair(42, 42);
if (p instanceof Pair(String s, String t)) {
  System.out.println(s + ", " + t);
} else {
  System.out.println("Not a pair of strings") ;
}

以上是关于record 模式的所有内容。

2. switch模式匹配

该功能最初由 JEP 406(JDK 17)提出,后经 JEP 420(JDK 18)、427(JDK 19)和 433(JDK 20)改进。它与 "1. record模式 "功能(JEP 440)共同发展。

先来看下如下这段代码

Object obj = 100L ;
if (obj instanceof Integer) {
  Integer i = (Integer) obj ;
  obj = String.format("int %d", i);
} else if (obj instanceof Long) {
  Long l = (Long) obj ;
  obj = String.format("long %d", l);
} else if (obj instanceof String) {
  String s = (String) obj ;
  obj = String.format("String %s", s);
}

有个instanceof 模式以后就可以简化这样了

Object obj = 100L ;
if (obj instanceof Integer i) {
  obj = String.format("int %d", i);
} else if (obj instanceof Long l) {
  obj = String.format("long %d", l);
} else if (obj instanceof String s) {
  obj = String.format("String %s", s);
}
System.out.printf("result obj = %s%n", obj) ;

注意:上面的代码有2个问题

  1. 上面的代码有如果没有编译器的作用,那么它的时间复杂度将是O(n)
  2. 隐藏了一个BUG,当if,else没有判断到某个类型时可能会出现问题上面的代码并没有else,因为不强制所以当判断遗漏了某种类型时可能会给程序带来潜在的问题。

从Java 21开始,我们可以如下处理上面的if.. else 

var ret = switch (obj) {
  case Integer i -> String.format("int %d", i);
  case Long l    -> String.format("long %d", l);
  case String s  -> String.format("String %s", s);
  default        -> obj.toString() ;
};
System.out.printf("result ret = %s%n", ret) ;

在过去我们知道如果switch的每个case没有break或者return,那么它会穿透到下一个case直到遇到break或return。并且在传统的switch中没有default也是可以的。但是在上面的代码中必须要有default子句。

2.1 switch与null值

传统上,如果switch表达式值为空,switch 语句和表达式会抛出 NullPointerException,因此必须在 switch 之外进行空判断:

String s = null ;
switch (s) {
  // 如果不清楚这里的语法,你应该先看看java14对switch新语法的介绍
  case "a", "b" -> System.out.println("a or b") ;
  default -> System.out.println("defualt value") ;
}

控制台输出

图片

在上面的代码中在过去,我们要先对s进行null的判断,再进行switch,否则有可能就会出现上面的错误。修改如下:

if (s == null) {
  return ;
}
switch (s) {
  // TODO
}

以上代码是Java 21之前,从Java 21起,我们可以如下:

switch (s) {
  case null -> System.out.println("oops") ;
  case "a", "b" -> System.out.println("a or b") ;
  default -> System.out.println("defualt value") ;
}

无需单独的if判断是否为null情况。

2.2 switch条件判断

在case中还可以添加if...else判断

static void fn1(String resp) {
  switch (resp) {
    case String s -> {
      if (s.equalsIgnoreCase("success"))
        System.out.println("处理成功");
      else if (s.equalsIgnoreCase("failure"))
        System.err.println("处理失败");
      else
        System.out.println("未知结果") ;
    }
  }
}

在case中是使用when子句

static void fn2(String resp) {
  switch (resp) {
    case null -> {}
    case String s 
    when s.equalsIgnoreCase("success") -> {
      System.out.println("处理成功");
    }
    case String s
    when s.equalsIgnoreCase("failure") -> {
      System.err.println("处理失败");
    }
    case String s -> {
        System.out.println("未知结果") ;
    }
  }
}

这样,switch的可读性就更强了。

2.3 switch与enum常量

在Java 21之前,switch的case表达式必须是枚举类型,标签必须是枚举常量的简单名称,如下示例:

public enum Color { RED, BLUE, GREEN }
public static void fn1(Color c) {
  switch (c) {
    case RED, BLUE -> System.out.println("我喜欢的颜色") ;
    case GREEN -> {
      // TODO
    }
    default -> System.out.println("我讨厌的颜色") ;
  }
}

上面说的标签必须是枚举常量的简单名称什么意思呢?就是说在java21之前使用枚举时的标签不能是下面这种写法:

case Color.GREEN -> {}

而从Java 21起可以使用这种语法。

3. 虚拟线程

关于虚拟线程请查看这篇文章:

【技术革命】JDK21虚拟线程来袭,让系统的吞吐量翻倍!

4. 字符串模版

注:这是一个预览功能

编译:javac --enable-preview --source 21 -Xlint:preview Xxx.java

运行:java --enable-preview Xxx

在开发中字符串相关的操作是非常非常多的,虽然Java 提供了多种字符串组成机制,但遗憾的是,所有机制都有缺点。

  • 使用+操作符拼接字符串,看着都不好理解
String result = x + " + " + y + " = " + (x + y) ;
  • 冗余的StringBuilder
String s = new StringBuilder().append(x).append(" + ")
  .append(y).append(" = ").append(x + y).toString() ;
  • String#format 与 String#formatted将格式字符串与参数分离,避免了类型错配:
int x = 10, y = 20 ;
String s = String.format("%2$d + %1$d = %3$d", x, y, x + y);
String t = "%2$d + %1$d = %3$d".formatted(x, y, x + y) ;
  • java.text.MessageFormat要求太多,而且格式字符串中使用了不熟悉的语法:
String ret = MessageFormat.format("{0} + {1} = {2}", x, y, x + y) ;

4.1 STR 模板处理器

STR 是 Java 平台定义的模板处理器。它通过用表达式的(字符串化)值替换模板中的每个嵌入表达式来执行字符串插值。

String firstName = "Bill" ;
String lastName  = "Duck" ;
String fullName  = STR."\{firstName} \{lastName}" ;
System.out.println(fullName) ;

输出结果

Bill Duck

注:STR 是一个公共静态最终字段,会自动导入到每个 Java 源文件中。

表达式还可以执行相应的操作,如下:

int x = 10, y = 20 ;
String result = STR."\{x} + \{y} = \{x + y}" ;
System.out.println(result) ;
// 10 + 20 = 30

表达式中还可以调用方法

static String getName() {
  return "张三" ;
}
static record Req(String date, String time) {}
static void fn5() {
  String s = STR."我的名字是 \{getName()} ";
  System.out.println(s) ;
  Req req = new Req("2000-01-01", "23:59:59") ;
  String t = STR."Access at \{req.date} \{req.time}";
  System.out.println(t) ;
}

输出结果

我的名字是 张三
Access at 2000-01-01 23:59:59

多行模版字符串

static void fn6() {
  String name    = "张三";
  String phone   = "1899999999";
  String address = "xxxooo";
  String json = STR."""
  {
    "name":    "\{name}",
    "phone":   "\{phone}",
    "address": "\{address}"
  }
  """;
  System.out.println(json);
}

输出结果

{
   "name": "张三",
   "phone": "1899999999",
   "address": "xxxooo"
}

以上是基于STR模版处理器的内容,接下来介绍另外一个。

4.2 FMT 模板处理器

FMT 是 Java 平台定义的另一种模板处理器。FMT 与 STR 类似,它执行插值,但也解释嵌入式表达式左侧的格式规范。格式说明符与 java.util.Formatter 中定义的格式说明符相同。

record Rectangle(String name, double width, double height) {
  double area() {
    return width * height;
  }
}
public static void main(String[] args) {
  Rectangle[] zone = new Rectangle[] {
    new Rectangle("Alfa", 17.8, 31.4),
    new Rectangle("Bravo", 9.6, 12.4),
  };
  String s = 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()}
    \{" ".repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()}
  """;
  System.out.println(s) ;
}

5. 序列集合

在Java21 之前的集合类中要获取第一个和最后一个元素,不同的集合操作方式不同或者压根就没有对应的方法。如下示例:

图片

在说遍历集合,正向时(从第一个到最后一个)操作方法基本一致。但是反向时遍历时每个集合就又不相同了。

在JDK21中提供了如下3个序列接口:

  • SequencedCollection
public interface SequencedCollection extends Collection {
  SequencedCollection reversed() ;
  default void addFirst(E e) ;
  default void addLast(E e) ;
  default E getFirst() ;
  default E getLast() ;
  default E removeFirst() ;
  default E removeLast() ;
}
  • SequencedSet
public interface SequencedSet extends SequencedCollection, Set {
  SequencedSet reversed();
}
  • SequencedMap
public interface SequencedMap extends Map {
  SequencedMap reversed() ;
  default Map.Entry firstEntry() ;
  default Map.Entry lastEntry() ;
  default Map.Entry pollFirstEntry() ;
  default Map.Entry pollLastEntry() ;
  default V putFirst(K k, V v) ;
  default V putLast(K k, V v) ;
  // other
}

以上3个集合都提供了对应的获取第一个和最后一个元素的方法及集合反转方法。上面定义的三个新接口与现有的集合类型层次结构非常吻合,如下图:

图片

对现有的类和接口进行了如下调整:

  • List 现在将 SequencedCollection 作为其直接超接口、
  • Deque 现在将 SequencedCollection 作为其直接超接口、
  • LinkedHashSet 进一步实现了 SequencedSet、
  • SortedSet 现在将 SequencedSet 作为其直接超接口、
  • LinkedHashMap 进一步实现了 SequencedMap,而
  • SortedMap 现在将 SequencedMap 作为其直接超接口。

6. 未命名模式&变量

注:这是一个预览功能

先看下面这个示例

public record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint cp) {}
  
Object obj = new Rectangle(
    new ColoredPoint(new Point(10, 10), Color.RED)
  ) ;
if (obj instanceof Rectangle(ColoredPoint(Point(int x, int y), Color c))) {
  System.out.printf("x = %d, y = %d%n", x, y) ;
}

在上面的if判断中,对于Color c变量并没有使用,从Java 21开始我们可以像下面这样改写:

if (obj instanceof Rectangle(ColoredPoint(Point(int x, int y), _))) {
  System.out.printf("x = %d, y = %d%n", x, y) ;
}

使用一个 "_" 下划线代替即可。

未使用的变量

int[] arr = {1, 2, 3, 4, 5} ;
int total = 0 ;
for (var a : arr) {
  total++ ;
}

在这个示例中,变量a并没有使用,所以从Java 21开始可以改写如下:

for (var _ : arr) {
  total++ ;
}

对于这样没有使用的变量,我们可以用一个 "_" 下划线代替。其它示例:

try {
  int a = 1 / 0 ;
} catch (Exception _) { // 这里没有用到异常通过可以使用 _
}

注:我用的Eclipse没法直接使用,我这里是通过记事本编写,通过命令行编译&运行。

7. 未命名的类&Main方法

注:这是一个预览功能

下面这个代码是学习java的入门代码

public class UnnamedClassAndMain {
 public static void main(String[] args) {
   System.out.println("Hello World!!!") ;
 }
}

从Java 21开始,我们可以简化成如下形式了

public class UnnamedClassAndMain {
  void main() {
    System.out.println("Hello World!!!") ;
  }
}

未命名的类

还是拿上面的程序演示,我们还可以继续简化如下形式:

void main() {
  System.out.println("Hello World!!!") ;
}

对,文件中只有一个极简的方法,连类的声明都没有了。你甚至还可以如下,定义方法,方法调用

String name = "Pack" ;
String getName() {
  return name ;
}
void main() {
  System.out.println(getName()) ;
}

类文件直接定义方法,声明变量。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

要不要升级?Java 21强大的新特性,代码量减半

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

要不要升级?Java 21强大的新特性,代码量减半

FMT 是 Java 平台定义的另一种模板处理器。FMT 与 STR 类似,它执行插值,但也解释嵌入式表达式左侧的格式规范。格式说明符与 java.util.Formatter 中定义的格式说明符相同。
FMTJavaSTR2024-11-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录