变差函数的编译目的是什么



在Java中,可变参数方法由编译器重新编写,使它们成为采用期望可变参数的数组的方法(根据此答案)。

Scala会发生什么?

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

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

以下内容?

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

作为一个后续问题:varadic方法实现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编译器对varargs的编码不同于Java编译器(当然,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方法将始终涉及在Seq上调用toArray,而在从Scala中调用Scala varargs时不会发生这种情况(无论它们是否使用@varargs注释以实现Java兼容性)。

最新更新