当前位置: 首页 > 知识库问答 >
问题:

新的Java17型模式匹配开关如何在引擎盖下工作?

袁元明
2023-03-14

新的Java17型模式匹配开关如何在引擎盖下工作?由于该功能相当新,本问题不讨论它。

提醒:要使此代码在Java 17下工作,需要启用预览功能

public static void test (Object o) {
  System.out.println(switch(o){
    case Number n -> "number " + n;
    case Enum e -> "enum " + e;
    case String s -> "string " + s;
    default -> "other " + o;
  });
}

使用javap-c对上述代码进行了反汇编:

   0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: aload_0
   4: dup
   5: invokestatic  #13                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
   8: pop
   9: astore_1
  10: iconst_0
  11: istore_2
  12: aload_1
  13: iload_2
  14: invokedynamic #19,  0             // InvokeDynamic #0:typeSwitch:(Ljava/lang/Object;I)I
  19: tableswitch   { // 0 to 2
                 0: 44
                 1: 58
                 2: 74
           default: 90
      }
  44: aload_1
  45: checkcast     #23                 // class java/lang/Number
  48: astore_3
  49: aload_3
  50: invokedynamic #25,  0             // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/Number;)Ljava/lang/String;
  55: goto          96
  58: aload_1
  59: checkcast     #29                 // class java/lang/Enum
  62: astore        4
  64: aload         4
  66: invokedynamic #31,  0             // InvokeDynamic #2:makeConcatWithConstants:(Ljava/lang/Enum;)Ljava/lang/String;
  71: goto          96
  74: aload_1
  75: checkcast     #34                 // class java/lang/String
  78: astore        5
  80: aload         5
  82: invokedynamic #36,  0             // InvokeDynamic #3:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
  87: goto          96
  90: aload_0
  91: invokedynamic #39,  0             // InvokeDynamic #4:makeConcatWithConstants:(Ljava/lang/Object;)Ljava/lang/String;
  96: invokevirtual #42                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  99: return

鉴于这一关键点:

  14: invokedynamic #19,  0             // InvokeDynamic #0:typeSwitch:(Ljava/lang/Object;I)I

看起来Java在自动生成的int-typeSwitch(object,int)方法中将对象转换为int,我看不到生成的代码。这种转换的目标是能够在int值上使用常规的切换表。

起初,我认为使用了的hashCode,即对象。getClass()。hashCode(),就像切换字符串一样,但实际上类对象的哈希代码在JVM的连续运行中并不是常数。因此,除非在每次执行时都重建映射表,否则这是不可能的,我怀疑的是。

因此,问题是:

这个typeSwitch方法做什么?

它是否比一系列if instanceof或if instanceof更聪明?这可能吗?

传递额外int的目的是什么?

共有1个答案

宦正诚
2023-03-14

这个typeSwitch()方法做什么?

invokeDynamic指令(第一次命中时)调用SwitchBootstraps。typeSwitch()方法。然后,此方法返回应该执行的方法调用(这是InvokedDynamic通常所做的)。

SwitchBootstraps的最后一个参数。typeSwitch()方法(标签参数)在本例中是开关中的类列表:编号。类,枚举。类,字符串。类别

SwitchBootstraps.typeSwitch()bootstrap方法检查标签参数的正确性,然后为执行有效处理的SwitchBootstraps.doTypeSwitch()方法返回ConstantCallSite(例如,调用动态指令的最终执行)。

如果您查看SwitchBootstraps.doTypeSwitch()的作用:它遍历类列表并返回第一个找到的匹配项。

额外传递的int的目的是什么?

由于这种情况,需要附加参数(startIndex):

public static void test(Object o) {
    System.out.println(switch(o){
        case Number n && n.intValue() >= 10 -> "large number " + n;
        case Number n -> "small number "+ n;
        case Enum e -> "enum " + e;
        case String s -> "string " + s;
        default -> "other " + o;
    });
}

现在有两种情况与类java.lang.Number匹配,第一种情况由条件n保护

如果我调用test(5),则将使用要测试的对象(Integer.valueOf(5))调用doTypeSwitch()方法,即0的startIndex和类列表。它迭代类列表,并在找到第一个编号条目并返回其索引(0)时立即停止。

它开始处理第一个案例并检查防护装置。因为我用值5调用它,所以保护失败,代码循环回doTypeSwitch()调用。然而,这次它是用对象来调用的,以测试(Integer.valueOf(5)),一个1的开始索引和相同的类列表。

代码是否比列表更智能(或更快)if(o instanceof Number n

在当前状态下肯定不会更快。但是请记住,这是第一次迭代(https://openjdk.java.net/jeps/406)中的预览功能:Java架构师正在试验此功能,获得typeSwitch的最佳性能可能不是第一个问题。

这个特性已经有了第二次迭代(https://openjdk.java.net/jeps/420),一旦设计完成,未来的版本可能会提高性能。

但即便如此:要使用if/else if链实现测试(对象o)方法,您必须将该方法分为两部分:

public static void test2(Object o) {
    System.out.println(check2(o));
}

private static String check2(Object o) {
    if (o instanceof Number n && n.intValue() >= 10)
        return "large number " + n;
    else if (o instanceof Number n)
        return "small number "+ n;
    else if (o instanceof Enum e)
        return "enum " + e;
    else if (o instanceof String s)
        return "string " + s;
    else
        return "other " + o;
}

这大约是行数的两倍。

那(让它更聪明)可能吗?

这至少是可以想象的。可以重新排列类标签,可以根据类标签的类层次结构将它们做成树结构,或者根据最常用的路径在运行时执行一些其他智能操作。

 类似资料:
  • Java的switch语句是如何工作的?它如何将所使用变量的值与案例部分中给出的值进行比较?它是否使用或,还是完全是其他原因? 我主要对1.7之前的版本感兴趣。

  • 但我对西农在台面下是如何工作仍有一些疑问。我想我是在说Sinon,但这个问题可能适用于所有其他设计为mock/stub/spy的库。 在过去的几年里,我工作最多的语言是Java。在Java中,我使用Mockito来模拟/存根依赖项和依赖项注入。我曾经导入这个类,用注释这个字段,并将这个mock作为param传递给被测试的类。对我来说,很容易看出我在做什么:模仿一个类,并将模仿作为param传递。

  • @Component表示给定的类将是给定上下文中的单例,@aspect表示在运行时/编译期间,一个方面类的内容将以某种方式编织到目标类中--例如,这个目标类不是单例而是原型。我最后的结局是什么?

  • 除了阅读github中的代码之外,是否有关于signalr.redis包如何工作的白皮书类型的文档?具体地说,我想知道它为Redis添加了哪些键、更新/删除策略等。当查看Redis内部时,我只看到以下调用中指定的一个键(即“signalr.Redis.sample”): 这把钥匙好像是Redis的柜台。我假设正在创建其他键并迅速删除,以方便连接到Redis的每个应用服务器之间的消息。

  • 问题内容: 在阅读了戴夫·切尼(Dave Cheney)关于Go的地图的博客文章之后,对我来说,还有几件事尚不清楚。 TLDR: 为什么它们无序? 实际值存储在哪里? 深入研究运行时程序包后,我发现基本的映射结构如下: -是存储区数组,其中索引是键的哈希值的低位,其中存储区为: ..好吧,这只是每个项目是键的哈希值的第一个字节的数组。键值对存储为(每个存储桶八对)。但是到底在哪里?考虑到映射可能包

  • 问题内容: 想知道是否有一种方法可以在Swift中执行以下操作。 我知道我还有其他方法可以使这个人为的示例工作,例如是否使用开关,而是使用开关,但是我正在寻找一种解决方案,具体涉及将开关模式匹配到阵列。谢谢! 问题答案: 您可以定义一个自定义模式匹配运算符,该运算符 将一个数组作为“模式”和一个值: 类似的运算符已经存在,例如间隔: