可变参数函数被编译成什么?

What do variadic functions get compiled to?

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

Scala 中发生了什么?

我主要关心的是,如果传递或不传递另一种类型的集合,可变参数是否被隐式复制到 Array,即编译器是否会以某种方式重写此片段:

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

到以下?

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

作为后续问题:可变参数方法实现 Java 接口还是纯 Scala 是否存在差异?

您可以使用 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 编译器对可变参数的编码与 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: _*)
}

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

总结一下:使用 seq: _* 从 Scala 调用 Java 可变参数方法将始终涉及 toArraySeq 上被调用,而这不会发生从 Scala 调用 Scala varargs 方法时(无论它们是否用 @varargs 注释以实现 Java 兼容性)。