写点什么

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:004904

评论

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

华为云Serverless可观测性解决方案打造高效、可靠的云原生应用

华为云PaaS服务小智

Serverless 华为云

客户在哪儿AI生产的企业全历史行为数据是ToB大客户营销的最佳抓手

客户在哪儿AI

ToB营销 ToB获客 ToB增长 大客户营销

开个技术外挂|用技术轻松实现GPU显卡冷却风扇噪声控制

Altair RapidMiner

gpu 仿真 显卡 GPU实例 altair

告别手动做PPT!这5款AI一键生成PPT软件,你都知道吗?

彭宏豪95

人工智能 效率工具 PPT AIGC AI生成PPT

快手可灵视频生成大模型全方位测评

快手技术

音视频技术 #大模型

最佳产品奖,TeleDB拿下!

天翼云开发者社区

数据库 云计算

KaiwuDB CTO 魏可伟:差异化创新,面向行业的多模架构

KaiwuDB

多模数据库 KaiwuDB 新版本发布

“夺金”2024中国互联网大会!天翼云斩获三项大奖!

天翼云开发者社区

云计算 互联网大会

促进云边协同发展,我们一直在努力!

天翼云开发者社区

云计算 算力

盘点那些国际知名的黑客(下)

网络安全服务

技术 黑客 计算机 黑客攻击 女性

如何根据淘宝买家秀API返回值优化商品详情页

技术冰糖葫芦

API Explorer api 货币化 API 文档

基于Java+SpringBoot+Vue前后端分离常规应急物资管理系统设计和实现

hunter_coder

后端开发

Tele-FLM系列再升级!52B对话模型发布、全球首个万亿单体稠密模型开源

智源研究院

【YashanDB知识库】YashanDB的JDBC/OCI驱动如何设置字符编码

YashanDB

yashandb 崖山数据库 崖山DB

如何搭建设备巡检二维码?看看这篇教程

草料二维码

亚信安慧AntDB亮相PostgreSQL中国技术大会,获“数据库最佳应用奖”并分享数据库应用实践

亚信AntDB数据库

“分离”“聚合”两手抓,天翼云聚合计算赋能多元化应用场景!

天翼云开发者社区

云计算 天翼云

赋能未来教育,3DCAT实时云渲染助力深圳鹏程技师学院打造5G+XR实训室

3DCAT实时渲染

实时云渲染 虚拟仿真云教学 虚拟仿真实训教学

全面解析淘宝商品详情API的SKU信息

技术冰糖葫芦

API Explorer API 编排 api 货币化 API 文档 pinduoduo API

某能源自保公司:携手嘉为蓝鲸WeOps,共赴运维转型升级之路!

嘉为蓝鲸

数字化转型 可观测 自动化运维

面试官:聊聊你对分库分表的理解?

王磊

Java

DataCanvas Alaya九章元识大模型通过北京市生成式人工智能服务备案

九章云极DataCanvas

热度继续!从零到一,和亚马逊云科技一起快速启动属于你的大模型

科技热闻

中国人民大学商学院校友IT互联网+俱乐部会长董哲一行到访亚信科技,共探企业创新之路

亚信AntDB数据库

基于Java+SpringBoot+Vue前后端分离宠物商城网站设计和实现

hunter_coder

后端开发

基于Java+SpringBoot+Vue前后端分离车辆管理系统设计和实现

hunter_coder

后端开发

MCtalk·CEO对话×酷家乐:从智能涌现到应用涌现,AI在B端做了哪些事?

ToB行业头条

如何挑选最佳多项目进度管理软件?

爱吃小舅的鱼

项目进度 项目进度管理

亚信安慧正式加入多样性算力产业及标准推进委员会

亚信AntDB数据库

基于Java+SpringBoot+Vue前后端分离成绩管理系统设计和实现

hunter_coder

后端开发

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