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

时间:2019-01-23 08:51:15

标签: scala variadic-functions variadic

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

在Scala中会发生什么?

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

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

到以下?

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

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

1 个答案:

答案 0 :(得分:4)

您可以使用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一无所知)。您可以强制Scala编译器生成带有@varargs批注的Java兼容的varargs方法:

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 varargs方法将始终涉及toArray上的Seq的调用,而从以下位置调用Scala varargs方法时不会发生这种情况Scala(出于Java兼容性,它们是否带有@varargs注释)。