【问题标题】:What do variadic functions get compiled to?可变参数函数编译成什么?
【发布时间】:2019-01-23 08:51:14
【问题描述】:

在 Java 中,可变参数方法由编译器重新编写,以便它们成为采用预期可变参数参数的数组的方法(根据 this answer)。

在 Scala 中会发生什么?

我主要关心的是,如果传递了另一种类型的集合,是否将可变参数隐式复制到 Array,即编译器是否会以某种方式重新编写这个 sn-p:

val strings = Seq("hello")
"%s".format(strings: _*)

到以下?

val strings = Seq("hello")
"%s".format(strings.toArray: _*)

作为后续问题:可变参数方法是实现 Java 接口还是纯 Scala 有区别吗?

【问题讨论】:

    标签: scala variadic-functions variadic


    【解决方案1】:

    您可以使用javap -v 轻松查看此内容。如果您使用 2.12 编译以下代码(现在使用 String.format 而不是 "%s".format):

    class Example1 {
      val strings = Seq("foo")
      def formatResult = String.format("%s", strings: _*)
    }
    

    你会得到这个:

      public java.lang.String formatResult();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=4, locals=1, args_size=1
             0: ldc           #21                 // String %s
             2: aload_0
             3: invokevirtual #23                 // Method strings:()Lscala/collection/Seq;
             6: getstatic     #29                 // Field scala/reflect/ClassTag$.MODULE$:Lscala/reflect/ClassTag$;
             9: ldc           #31                 // class java/lang/String
            11: invokevirtual #35                 // Method scala/reflect/ClassTag$.apply:(Ljava/lang/Class;)Lscala/reflect/ClassTag;
            14: invokeinterface #41,  2           // InterfaceMethod scala/collection/Seq.toArray:(Lscala/reflect/ClassTag;)Ljava/lang/Object;
            19: checkcast     #43                 // class "[Ljava/lang/Object;"
            22: invokestatic  #47                 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
            25: areturn
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      26     0  this   LExample1;
    

    是的,它会将strings 转换为数组。

    如果你使用"%s".format,不过,像这样:

    class Example2 {
      val strings = Seq("foo")
      def formatResult = "%s".format(strings: _*)
    }
    

    您不会看到转换:

      public java.lang.String formatResult();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=4, locals=1, args_size=1
             0: new           #21                 // class scala/collection/immutable/StringOps
             3: dup
             4: getstatic     #27                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
             7: ldc           #29                 // String %s
             9: invokevirtual #33                 // Method scala/Predef$.augmentString:(Ljava/lang/String;)Ljava/lang/String;
            12: invokespecial #37                 // Method scala/collection/immutable/StringOps."<init>":(Ljava/lang/String;)V
            15: aload_0
            16: invokevirtual #39                 // Method strings:()Lscala/collection/Seq;
            19: invokevirtual #43                 // Method scala/collection/immutable/StringOps.format:(Lscala/collection/Seq;)Ljava/lang/String;
            22: areturn
          LineNumberTable:
            line 14: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      23     0  this   LExample2;
    

    这是因为 Scala 编译器对 varargs 的编码与 Java 编译器不同(后者当然对 Scala 的Seq 一无所知)。您可以使用 @varargs 注释强制 Scala 编译器生成与 Java 兼容的可变参数方法:

    class Example3 {
      def foo(xs: String*): Unit = ()
    
      @annotation.varargs
      def bar(xs: String*): Unit = ()
    
      val strings = Seq("foo")
      def fooResult = foo(strings: _*)
      def barResult = bar(strings: _*)
    }
    

    请注意,这会生成 both 编码,因此bar(strings: _*) 仍然不会涉及数组转换,因为在这种情况下,Scala 编译器会选择 Scala 编码方法。

    总结一下:使用 seq: _* 从 Scala 调用 Java 可变参数方法将始终涉及在 Seq 上调用 toArray,而从 Scala 调用 Scala 可变参数方法时不会发生这种情况(无论它们是否'为了与 Java 兼容而使用 @varargs 注释)。

    【讨论】:

    猜你喜欢
    • 2019-03-08
    • 2021-11-28
    • 2016-07-24
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多