QCon北京|3天沉浸式学习,跳出信息茧房。 了解详情
写点什么

Java 新特性完整指南:Switch 模式匹配

  • 2023-07-18
    北京
  • 本文字数:10257 字

    阅读完需:约 34 分钟

Java新特性完整指南:Switch模式匹配

Switch 语句由选择器表达式和包含 case 标签switch 块组成;对选择器表达式进行求值,并切换到与求值结果相匹配的 case 标签所对应的执行路径。


在原来 switch 语句中,case…:标签语法采用的是穿透语义(fall-through semantics)。Java 14 增加了对新标签语法case ...-> 的支持,该语法采用的是非穿透语义。


Java 14 还增加了对 switch 表达式的支持。Switch 表达式的计算结果为单个值。该版本还引入了yield语句,用于显式地生成一个值。


支持 switch 表达式(另有一篇文章进行了详细讨论)是指可以将 switch 用于需要表达式(如赋值语句)的实例。

问题


即使有了 Java 14 所做的功能增强,switch 语句的使用还是有一些限制:


  1. Switch 选择器表达式只支持特定类型,即基本整型数据类型byteshortcharint;对应的装箱形式ByteShortCharacterIntegerString类;枚举类型。

  2. Switch 选择器表达式的计算结果只能与常量做相等比较。在匹配 case 标签和常量值时只对一个值进行检查。

  3. null 值的处理方式与其他值不同。

  4. 错误处理方式不统一。

  5. 枚举的作用域不是很合理。

解决方案


为了克服这些限制,人们已经提出并实现了一种实用、便捷的解决方案:switch 语句模式匹配和表达式。这个解决方案解决了上面提到的所有问题。


Switch 模式匹配是在 JDK 17 中引入的,JDK 18、19 和 20 对其做了改进,JDK 21 最终将其完成。


模式匹配从以下几个方面克服了传统 switch 语句的局限性:


  1. 选择器表达式的类型可以是整型基本类型(不包括long类型),也可以是任何引用类型。

  2. 除了常量之外,case 标签还可以包含模式。不同于常量 case 标签只能应用于一个值,模式 case 标签可以应用于多个值。引入了一个新的 case 标签case p,其中p是一个模式。

  3. Case 标签可以包含null

  4. Case 标签后面有一个可选的when子句,可用于条件模式匹配或受保护模式匹配。带有 when 的 case 标签被称为受保护 case 标签。

  5. 枚举常量的 case 标签可以限定。当使用枚举常量时,选择器表达式不一定要是枚举类型。

  6. 引入MatchException,在模式匹配中实现更统一的错误处理。

  7. 传统的 switch 语句和穿透语义也支持模式匹配。模式匹配的一个好处是方便面向数据的编程,例如提高复杂数据查询的性能。

什么是模式匹配?


模式匹配是一个功能强大的特性,它扩展了程序中控制流结构的功能。除了可以匹配传统上支持的常量外,该特性还允许选择器表达式与多个模式进行匹配。Switch 语句的语义并没有变化;与 switch 选择器表达式的值进行匹配的 case 标签可能包含模式,如果选择器表达式的值与一个 case 标签模式匹配成功,就会选中控制流中那个 case 标签所对应的执行路径。唯一的增强是,选择器表达式既可以是基本整型(不包括 long 类型),也可以是任何引用类型。除了常量之外,case 标签还可以包含模式。此外,还有一个新增功能是,case 标签支持 null 和限定枚举常量。


以下是 switch 块中 switch 标签的语法:


SwitchLabel:  case CaseConstant { , CaseConstant }  case null [, default]  case Pattern  default
复制代码


模式匹配既可以用于具有穿透语义的传统case…:标签语法,也可以用于非穿透语义的case…->标签语法。尽管如此,必须注意的是,一个 switch 块不能同时使用这两种类型的 case 标签。


得益于这些修改,模式匹配让开发人员可以实现更复杂的控制流结构,为代码逻辑的处理提供了更丰富的方法。

环境设置


要运行本文中的示例代码,唯一的先决条件是安装 Java 20 或 Java 21。与 Java 20 相比,Java 21 只做了一项增强,即在 case 标签中支持限定枚举常量。可以通过以下命令查看 Java 版本:


java --versionjava version "20.0.1" 2023-04-18Java(TM) SE Runtime Environment (build 20.0.1+9-29)Java HotSpot(TM) 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing)
复制代码


因为在 Java 20 中,switch 模式匹配是一个预览特性,所以必须使用以下语法运行javacjava命令:


javac --enable-preview --release 20 SampleClass.javajava --enable-preview  SampleClass
复制代码


但是,也可以使用源码启动器直接运行它,命令行如下:


java --source 20 --enable-preview Main.java
复制代码


还有一个jshell选项,但也需要启用预览功能:


jshell --enable-preview
复制代码

一个简单的模式匹配示例


我们从一个简单的模式匹配示例开始,其中,switch 表达式的选择器表达式类型是引用类型Collection ;case 标签包含case p形式的模式。


import java.util.Collection;import java.util.LinkedList;import java.util.Stack;import java.util.Vector;
public class SampleClass { static Object get(Collection c) {
return switch (c) { case Stack s -> s.pop(); case LinkedList l -> l.getFirst(); case Vector v -> v.lastElement(); default -> c; }; }
public static void main(String[] argv) {
var stack = new Stack<String>(); stack.push("firstStackItemAdded"); stack.push("secondStackItemAdded"); stack.push("thirdStackItemAdded");
var linkedList = new LinkedList<String>();
linkedList.add("firstLinkedListElementAdded"); linkedList.add("secondLinkedListElementAdded"); linkedList.add("thirdLinkedListElementAdded");
var vector = new Vector<String>();
vector.add("firstVectorElementAdded"); vector.add("secondVectorElementAdded"); vector.add("thirdVectorElementAdded");
System.out.println(get(stack)); System.out.println(get(linkedList)); System.out.println(get(vector)); }}
复制代码


编译并运行这个 Java 应用程序,输出如下:


thirdStackItemAddedfirstLinkedListElementAddedthirdVectorElementAdded
复制代码

模式匹配支持所有引用类型


在上面给出的示例中,选择器表达式的类型是Collection类类型。但是,选择器表达式的类型可以是任何引用类型。因此,case 标签模式可以是与选择器表达式的值兼容的任何引用类型。例如,下面是经过修改的SampleClass类,它使用了Object类型的选择器表达式,而 case 标签模式除了之前使用的StackLinkedListVector等引用类型外,还包括一个记录模式和一个数组引用类型的模式。


import java.util.LinkedList;import java.util.Stack;import java.util.Vector;
record CollectionType(Stack s, Vector v, LinkedList l) {}
public class SampleClass { static Object get(Object c) { return switch (c) { case CollectionType r -> r.toString(); case String[] arr -> arr.length; case Stack s -> s.pop(); case LinkedList l -> l.getFirst(); case Vector v -> v.lastElement(); default -> c; }; }
public static void main(String[] argv) {
var stack = new Stack<String>(); stack.push("firstStackItemAdded"); stack.push("secondStackItemAdded"); stack.push("thirdStackItemAdded");
var linkedList = new LinkedList<String>();
linkedList.add("firstLinkedListElementAdded"); linkedList.add("secondLinkedListElementAdded"); linkedList.add("thirdLinkedListElementAdded");
var vector = new Vector<String>();
vector.add("firstVectorElementAdded"); vector.add("secondVectorElementAdded"); vector.add("thirdVectorElementAdded");
var r = new CollectionType(stack, vector, linkedList); System.out.println(get(r)); String[] stringArray = {"a", "b", "c"};
System.out.println(get(stringArray)); System.out.println(get(stack)); System.out.println(get(linkedList)); System.out.println(get(vector));
}}
复制代码


这次的输出如下:


CollectionType[s=[firstStackItemAdded, secondStackItemAdded, thirdStackItemAdded], v=[firstVectorElementAdded, secondVectorElementAdded, thirdVectorElementAdded], l=[firstLinkedListElementAdded, secondLinkedListElementAdded, thirdLinkedListElementAdded]]3thirdStackItemAddedfirstLinkedListElementAddedthirdVectorElementAdded
复制代码

Null case 标签


传统上,如果选择器表达式的计算结果为空,则 switch 语句在运行时会抛出NullPointerException。选择器表达式为空不是编译时问题。下面这个简单的应用程序有一个匹配所有 case 标签的default ,我们通过它演示下选择器表达式为空如何导致运行时异常NullPointerException


import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { default -> c; }; }
public static void main(String[] argv) { get(null); }}
复制代码


我们可以在 switch 块外面显式地检测空值,并仅在值非空时执行 switch,但这涉及到添加 if-else 代码。在新的模式匹配特性中,Java 增加了对null的支持。下面这个应用程序中的 switch 语句使用case null来检测选择器表达式的值是否为空。


import java.util.Collection;
public class SampleClass { static void get(Collection c) {
switch (c) { case null -> System.out.println("Did you call the get with a null?"); default -> System.out.println("default"); } }
public static void main(String[] argv) { get(null); }}
复制代码


在运行时,应用程序输出如下:


你在调用get方法时使用了null参数?
复制代码


case null可以与case default合并,如下所示:


import java.util.Collection;
public class SampleClass { static void get(Collection c) { switch (c) { case null, default -> System.out.println("Did you call the get with a null?"); } }
public static void main(String[] argv) { get(null); }}
复制代码


但是,case null 不能与任何其他 case 标签合并。例如,下面的类将 case null 与一个模式为Stack s的 case 标签做了合并:


import java.util.Collection;import java.util.Stack;
public class SampleClass { static void get(Collection c) { switch (c) { case null, Stack s -> System.out.println("Did you call the get with a null?"); default -> System.out.println("default"); } }
public static void main(String[] args) { get(null); }}
复制代码


该类将产生如下编译时错误:


SampleClass.java:11: error: 非法case标签合并          case null, Stack s -> System.out.println("Did you call the get with a null?");
复制代码

带有 when 子句的受保护模式


有时,开发人员可能会使用与布尔表达式计算结果做匹配的条件式 case 标签模式。这时,when子句就派上用场了。该子句会计算布尔表达式,形成所谓的“受保护模式”。如下所示,代码中的第一个 case 标签使用when子句判断Stack是否为空。


import java.util.Stack;import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Stack s when s.empty() -> s.push("first"); case Stack s2 -> s2.push("second"); default -> c; }; }}
复制代码


对应的代码在-> 右侧,只有在 Stack 为空时才会执行。

对于带有模式的 case 标签,顺序很重要


在使用带模式的 case 标签时,开发人员必须确保不会因为顺序产生任何与类型或子类型层次结构相关的问题。这是因为,与常量 case 标签不同,case 标签中的模式使得选择器表达式可以匹配多个包含模式的 case 标签。Switch 模式匹配特性会匹配第一个模式与选择器表达式值相同的标签。


如果一个 case 标签模式的类型是在它之前出现的另一个 case 标签模式的类型的子类型,则会发生编译时错误,因为后一个 case 标签将被识别为不可访问代码。


下面是一个演示程序,你可以编译并运行它,其中类型为Object的 case 标签模式控制了后续类型为Stack的代码标签模式。


import java.util.Stack;
public class SampleClass { static Object get(Object c) { return switch (c) { case Object o -> c; case Stack s -> s.pop(); }; }}
复制代码


在编译这个类时,会产生以下错误信息:


SampleClass.java:12: error: 该case标签为它前面的case标签所控制        case Stack s  -> s.pop();             ^
复制代码


像下面这样对调两个 case 标签的顺序就可以修复这个编译时错误:


public class SampleClass {    static Object get(Object c) {        return switch (c) {            case Stack s  -> s.pop();            case Object o  -> c;        };    }}
复制代码


类似地,如果 case 标签包含的模式与前面出现的无条件/非保护模式 case 标签具有相同的引用类型,则会导致编译类型的错误,就像下面的类这样:


import java.util.Stack;import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Stack s -> s.push("first"); case Stack s2 -> s2.push("second"); }; }}
复制代码


上述代码在编译时会产生以下错误:


SampleClass.java:13: error: 该case标签为它前面的case标签所控制        case Stack s2 -> s2.push("second");             ^
复制代码


为了避免这种错误,case 标签的顺序应该直观、可读。应该首先列出常量标签,然后是case null标签、受保护的模式标签和非受保护类型的模式标签。default case 标签可以与case null 标签合并,也可以单独作为最后一个 case 标签。下面的类演示了正确的排序:


import java.util.Collection;import java.util.Stack;import java.util.Vector;
public class SampleClass { static Object get(Collection c) { return switch (c) { case null -> c; //case label null case Stack s when s.empty() -> s.push("first"); // 受保护case标签 case Vector v when v.size() > 2 -> v.lastElement(); // 受保护case标签 case Stack s -> s.push("first"); // 非受保护case标签 case Vector v -> v.firstElement(); // 非受保护case标签 default -> c; }; }}
复制代码

模式匹配可用于传统的 switch 语句和穿透语义


模式匹配特性与它是 switch 语句还是 switch 表达式无关。模式匹配也与使用穿透语义的case…:标签还是使用非穿透语义的case…->标签无关。在下面的示例中,模式匹配与 switch 语句而不是与 switch 表达式一起使用。case 标签使用了具有穿透语义的case…:。第一个 case 标签中的when子句使用了一个受保护的模式。


import java.util.Stack;import java.util.Collection;
public class SampleClass { static void get(Collection c) { switch (c) { case Stack s when s.empty(): s.push("first"); break; case Stack s : s.push("second"); break; default : break; } }}
复制代码

模式变量的作用域


模式变量是出现在 case 标签模式中的变量。模式变量的作用域仅限于出现在->箭头右侧的块、表达式或 throw 语句。请看下面的演示代码,default 中使用了来自它前面的 case 标签的模式变量。


import java.util.Stack;
public class SampleClass { static Object get(Object c) { return switch (c) { case Stack s -> s.push("first"); default -> s.push("first"); }; }}
复制代码


上述代码会产生以下编译错误:


import java.util.Collection;SampleClass.java:13: error: cannot find symbol        default -> s.push("first");                   ^  symbol:   variable s  location: class SampleClass
复制代码


出现在受保护 case 标签模式中的模式变量,其作用域包括 when 子句,如下所示:


import java.util.Stack;import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Stack s when s.empty() -> s.push("first"); case Stack s -> s.push("second"); default -> c; }; }}
复制代码


由于模式变量的作用域有限,所以相同的模式变量名可以跨多个 case 标签使用。前面的例子说明了这一点,其中模式变量s用在了两个不同的 case 标签中。


当处理具有穿透语义的 case 标签时,模式变量的作用域扩展到了:右侧的一组语句。这就是为什么在上一节中,使用模式匹配和传统的 switch 语句,可以在两个 case 标签中使用相同的模式变量名。但是,具有穿透语义的 case 标签声明模式变量会导致编译时错误。关于这一点,下面的类可以证明:


import java.util.Stack;import java.util.Vector;import java.util.Collection;
public class SampleClass { static void get(Collection c) { switch (c) { case Stack s : s.push("second"); case Vector v : v.lastElement(); default : System.out.println("default"); } }}
复制代码


第一个语句组中缺少break; 语句,如果第二个语句组中的模式变量v未初始化,则 switch 可能会在第二个语句组处失败。上述类会产生如下编译时错误:


SampleClass.java:12: error: 非法穿透到模式        case Vector v  : v.lastElement();             ^
复制代码


只需在第一个语句组中添加break; 语句就可以修复这个错误:


import java.util.Stack;import java.util.Vector;import java.util.Collection;
public class SampleClass { static void get(Collection c) { switch (c) { case Stack s : s.push("second"); break; case Vector v : v.lastElement(); default : System.out.println("default"); } }}
复制代码

每个 case 标签只能有一个模式


无论是类型为case…:的 case 标签,还是类型为case…->的 case 标签,都不允许在单个 case 标签中组合使用多个模式,否则会导致编译时错误。也许不太明显,在单个 case 标签中组合使用多个模式会导致非法穿透,如下所示:


import java.util.Stack;import java.util.Vector;import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Stack s, Vector v -> c; default -> c; }; }}
复制代码


上述代码会产生以下编译时错误:


SampleClass.java:11: error: 非法穿透到模式        case Stack s, Vector v -> c;                      ^
复制代码

一个 switch 块中只能有一个匹配所有的 case 标签


无论是 switch 语句还是 switch 表达式,在一个 switch 块中包含多个匹配所有的 case 标签都会导致编译时错误。匹配所有的 case 标签是指:


  1. 一个带有模式、可以无条件匹配选择器表达式的 case 标签

  2. default case 标签请看下面的演示类:


import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Collection coll -> c; default -> c; }; }}
复制代码


编译这个类会产生以下错误信息:


SampleClass.java:13: error: switch同时具有无条件模式和default标签        default -> c;        ^
复制代码

类型覆盖的穷尽性


穷尽性意味着 switch 块必须处理选择器表达式所有可能的值。穷尽性要求只有在下列一项或多项适用的情况下才能满足:


a) 使用模式 switch 表达式/语句;


b) 使用case null


c) 选择器表达式不属于以下遗留类型:charbyteshortintCharacterByteShortIntegerString或枚举类型。


为了实现穷尽性,如果子类型不多的话,则可以为选择器表达式类型的每个子类型添加 case 标签。然而,如果子类型众多,这种方法可能会很啰嗦;例如,为Object类型的选择器表达式的每个引用类型添加 case 标签,甚或为Collection类型的选择器表达式的每个子类型添加 case 标签,都是不可行的。


为了演示穷尽性要求,请看下面这个类:


import java.util.Collection;import java.util.Stack;import java.util.LinkedList;import java.util.Vector;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Stack s -> s.push("first"); case null -> throw new NullPointerException("null"); case LinkedList l -> l.getFirst(); case Vector v -> v.lastElement(); }; }}
复制代码


该类会产生以下编译时错误消息:


SampleClass.java:10: error: switch表达式未涵盖所有可能的输入值                return switch (c) {                       ^
复制代码


如下所示,只需要增加一个 default case 标签就可以解决这个问题:


import java.util.Collection;import java.util.Stack;import java.util.LinkedList;import java.util.Vector;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Stack s -> s.push("first"); case null -> throw new NullPointerException("null"); case LinkedList l -> l.getFirst(); case Vector v -> v.lastElement(); default -> c; }; }}
复制代码


像下面这样,如果匹配所有的 case 标签所带有的模式可以无条件匹配选择器表达式,那么就可以满足穷尽性要求,但它无法显式地处理任何子类型。


import java.util.Collection;
public class SampleClass { static Object get(Collection c) { return switch (c) { case Collection coll -> c; }; }}
复制代码


default case 标签可用于满足穷尽性,但如果选择器表达式可能的取值非常少,有时候可以避免使用它。例如,如果选择器表达式的类型为java.util.Vector,只需提供一个子类java.util.Stack的 case 标签模式就可以避免。类似地,如果选择器表达式是密封类类型,则只有在密封类类型的permits子句中声明的类需要由 switch 块处理。

Switch case 标签中的泛型记录模式


Java 20 增加了对 switch 语句/表达式中泛型记录模式类型参数推断的支持。作为一个例子,考虑以下泛型记录:


record Triangle<S,T,V>(S firstCoordinate, T secondCoordinate,V thirdCoordinate){};
复制代码


在下面的 switch 块中,推断出的 record 模式如下:


Triangle<Coordinate,Coordinate,Coordinate>(var f, var s, var t): static void getPt(Triangle<Coordinate, Coordinate, Coordinate> tr){        switch (tr) {           case Triangle(var f, var s, var t) -> …;           case default -> …;        }}
复制代码

使用 MatchException 进行错误处理


Java 19 引入了java.lang.Runtime类的一个新的子类,旨在用更统一的方式处理模式匹配期间的异常。这个名为java.lang.MatchException的新类是一个预览 API。MatchException 不是专门为 switch 中的模式匹配而设计的,而是为所有模式匹配语言结构而设计的。当模式匹配最终未能匹配提供的任何模式时,在运行时可能就会抛出 MatchException。关于这一点,请看下面的应用程序。该应用程序的 case 标签中有一个 record 模式,而它所要匹配的 record 声明了一个除数为 0 的访问器方法。


record DivisionByZero(int i) {    public int i() {        return i / 0;    }}


public class SampleClass {
static DivisionByZero get(DivisionByZero r) { return switch(r) { case DivisionByZero(var i) -> r; };
}
public static void main(String[] argv) {
get(new DivisionByZero(42)); }}
复制代码


示例应用程序编译通过,没有错误,但运行时会抛出MatchException异常:


Exception in thread "main" java.lang.MatchException: java.lang.ArithmeticException: / by zero        at SampleClass.get(SampleClass.java:7)        at SampleClass.main(SampleClass.java:14)Caused by: java.lang.ArithmeticException: / by zero        at DivisionByZero.i(SampleClass.java:1)        at SampleClass.get(SampleClass.java:1)        ... 1 more
复制代码

小结


本文介绍了 Java 新增的对 switch 控制流结构模式匹配的支持。其主要改进是 switch 的选择器表达式可以是任何引用类型,并且 switch 的 case 标签可以包含模式,包括条件模式匹配。而且,如果你不愿意更新整个代码库,那么模式匹配也支持使用传统的 switch 语句及其穿透语义。


原文链接:

https://www.infoq.com/articles/pattern-matching-for-switch/

2023-07-18 08:005060

评论

发布
暂无评论
发现更多内容

STM32+移远MC20模块采用MQTT协议登录OneNet上传GPS数据

DS小龙哥

8月月更

Python 教程之输入输出(7)—— 如何在 Python 中不使用换行符进行打印?

海拥(haiyong.site)

Python 8月月更

开源一夏 | 对于jQuery选择器和动画效果停止动画的实战心得【前端jQuery框架】

恒山其若陋兮

开源 8月月更

Go-Excelize API源码阅读(三)——OpenReader()

Regan Yue

Go 开源 源码分析 8月日更 8月月更

jvm(一 )内存区域的划分

想要飞的猪

JVM JVM运行时数据区

2022-Java后端工程师面试指南-(SSM)

自然

spring cloud stream Java core 8月月更

2022-Java后端工程师面试指南-(消息队列)

自然

Rocket Rabbit MQ 8月月更

【LeetCode】算术三元组的数目Java题解

Albert

LeetCode 8月月更

Java+EasyExcel实现文件导入导出

Bug终结者

Java 8月月更

即将开幕!阿里云飞天技术峰会邀您一同探秘云原生最佳实践

阿里巴巴云原生

阿里云 云原生 阿里云飞天技术峰会

聊聊电源自动切换电路(常用自动切换电路总结)

矜辰所致

电路设计 8月月更 电源自动切换

全面了解Java中的15种锁概念及机制!

TimeFriends

8月月更

什么是 Office Open XML 文件格式

汪子熙

xml 微软 Office 8月月更 openOffice

Spring Security OAuth实现GitHub快捷登录

阿提说说

Spring Security OAuth

数据治理(二):数据治理功能方面

Lansonli

大数据 数据治理 8月月更

2022-Java后端工程师面试指南-(计算机网络)

自然

网络 8月月更

模块九(电商秒杀系统)

Geek_701557

毕业总结

Geek_701557

Spring(四、配置数据源)

开源 MySQ Druid 8月月更

SpringBoot实战:国际化组件MessageSource的执行逻辑与源码

看山

源码 spring源码 MessageSource Spring原理 SpringBoot实战

leetcode 232. Implement Queue using Stacks 用栈实现队列(简单)

okokabcd

LeetCode 数据结构与算法 栈和队列

低成本、大容量、高交互…Polkadot 引领 GameFi 实现新突破

One Block Community

区块链

Python爬虫eval混淆,爬虫进阶实战系列

梦想橡皮擦

Python 爬虫 8月月更

深入浅出边缘云 | 6. 监控与遥测

俞凡

架构 边缘计算 网络 深入浅出边缘云

MySQL 指令

武师叔

8月月更

详解中断系统

timerring

8月月更

一文带你搞懂OAuth2.0

闫同学

Go 后端 OAuth 2.0

企业文化如何治好“企业内耗”?

涛哥 数字产品和业务架构

企业文化 企业架构

《编程的原则》读书笔记(一):编程的前提和准则

Chares

软件工程 软件开发 程序开发 编程原理

OpenHarmony像素单位

坚果

开源 OpenHarmony 8月月更

关于架构的认知

yuexin_tech

架构

Java新特性完整指南:Switch模式匹配_编程语言_Deepak Vohra_InfoQ精选文章