Scala:为什么扩展特征会破坏 `Class.getConstructor()`?
Scala: Why does extending a trait break `Class.getConstructor()`?
当 class 扩展特征时,调用 getConstructor()
否则会成功然后抛出 NoSuchMethodException
。为什么会发生这种情况,可以采取什么措施?
在下面的代码中,第一次调用 getConstructor()
returns 没有错误。第二个引发异常。
trait MyTrait
class MyClass(root: String)
object Main extends App {
val one = new MyClass("foo")
one.getClass.getConstructor(classOf[String])
println("so far so good")
val two = new MyClass("foo") with MyTrait
two.getClass.getConstructor(classOf[String]) // NoSuchMethodException
}
new MyClass("foo") with MyTrait
创建一个带有零参数构造函数的新匿名 class(此构造函数在内部执行 super("foo")
),然后创建该新 class.
的实例
classOf[MyClass] eq one.getClass
classOf[MyClass] ne two.getClass
由于新的匿名 class 中没有基于 String
的构造函数,您的代码失败了。
让我们仔细看看会发生什么。
我稍微更改了您的定义以简化生成的输出。我的 Main
对象如下所示:
object App {
def main(args: Array[String]): Unit = {
val one = new MyClass("foo")
one.getClass.getConstructor(classOf[String])
println("so far so good")
val two = new MyClass("foo") with MyTrait
two.getClass.getConstructor(classOf[String]) // NoSuchMethodException
}
}
编译后我们得到以下classes: App$$anon.class
, App$.class
, App.class
, MyClass.class
, MyTrait.class
.
MyClass.class
和 MyTrait.class
是微不足道的,我不会讨论它们。
让我们看看有趣的 classes:
App.class
这是 javap -c
输出:
public final class test.App {
public static void main(java.lang.String[]);
Code:
0: getstatic #16 // Field test/App$.MODULE$:Ltest/App$;
3: aload_0
4: invokevirtual #18 // Method test/App$.main:([Ljava/lang/String;)V
7: return
}
如我们所见,它只是调用了 App$.main
方法。
App$.class
这是里面的内容:
public final class test.App$ {
public static test.App$ MODULE$;
public static {};
Code:
0: new #2 // class test/App$
3: invokespecial #14 // Method "<init>":()V
6: return
public void main(java.lang.String[]);
Code:
0: new #19 // class test/MyClass
3: dup
4: ldc #21 // String foo
6: invokespecial #24 // Method test/MyClass."<init>":(Ljava/lang/String;)V
9: astore_2
10: aload_2
11: invokevirtual #28 // Method test/MyClass.getClass:()Ljava/lang/Class;
14: iconst_1
15: anewarray #30 // class java/lang/Class
18: dup
19: iconst_0
20: ldc #32 // class java/lang/String
22: aastore
23: invokevirtual #36 // Method java/lang/Class.getConstructor:([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
26: pop
27: getstatic #41 // Field scala/Predef$.MODULE$:Lscala/Predef$;
30: ldc #43 // String so far so good
32: invokevirtual #47 // Method scala/Predef$.println:(Ljava/lang/Object;)V
35: new #7 // class test/App$$anon
38: dup
39: invokespecial #48 // Method test/App$$anon."<init>":()V
42: astore_3
43: aload_3
44: invokevirtual #28 // Method test/MyClass.getClass:()Ljava/lang/Class;
47: iconst_1
48: anewarray #30 // class java/lang/Class
51: dup
52: iconst_0
53: ldc #32 // class java/lang/String
55: aastore
56: invokevirtual #36 // Method java/lang/Class.getConstructor:([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
59: pop
60: return
private test.App$();
Code:
0: aload_0
1: invokespecial #54 // Method java/lang/Object."<init>":()V
4: aload_0
5: putstatic #56 // Field MODULE$:Ltest/App$;
8: return
}
最有趣的部分是第 35 和 39 行。
第 35 行告诉我们,实际上我们创建了 App$$anon
class 的一个实例。它是一个自动生成的 class 代表 MyClass("foo") with MyTrait
.
第 39 行告诉我们如何初始化它。我们调用 App$$anon."<init>":()V
构造函数,但不向其传递任何值。现在我很好奇 "foo"
常量在哪里?
App$$anon.class
这是里面的内容:
public final class test.App$$anon extends test.MyClass implements test.MyTrait {
public test.App$$anon();
Code:
0: aload_0
1: ldc #16 // String foo
3: invokespecial #19 // Method test/MyClass."<init>":(Ljava/lang/String;)V
6: return
}
在这里我们终于看到了我们的 "foo"
(第 1 行)。 ldc
表示"load constant"; “#16”是对 table 中那个常量的引用;评论说它的类型是 String
并且值是 foo
.
第 3 行是对 MyClass."<init>":(Ljava/lang/String;)V
的调用。
TLDR 优化器在自动生成的 class 的构造函数中隐藏常量。所以构造函数本身没有任何参数。
PS:如果我们这样修改代码:
val foo = "foo"
val two = new MyClass(foo) with MyTrait
然后我们得到一个自动生成的 class 和一个接受 String
参数的构造函数:
public final class test.App$$anon extends test.MyClass implements test.MyTrait {
public test.App$$anon(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokespecial #17 // Method test/MyClass."<init>":(Ljava/lang/String;)V
5: return
}
当 class 扩展特征时,调用 getConstructor()
否则会成功然后抛出 NoSuchMethodException
。为什么会发生这种情况,可以采取什么措施?
在下面的代码中,第一次调用 getConstructor()
returns 没有错误。第二个引发异常。
trait MyTrait
class MyClass(root: String)
object Main extends App {
val one = new MyClass("foo")
one.getClass.getConstructor(classOf[String])
println("so far so good")
val two = new MyClass("foo") with MyTrait
two.getClass.getConstructor(classOf[String]) // NoSuchMethodException
}
new MyClass("foo") with MyTrait
创建一个带有零参数构造函数的新匿名 class(此构造函数在内部执行 super("foo")
),然后创建该新 class.
classOf[MyClass] eq one.getClass
classOf[MyClass] ne two.getClass
由于新的匿名 class 中没有基于 String
的构造函数,您的代码失败了。
让我们仔细看看会发生什么。
我稍微更改了您的定义以简化生成的输出。我的 Main
对象如下所示:
object App {
def main(args: Array[String]): Unit = {
val one = new MyClass("foo")
one.getClass.getConstructor(classOf[String])
println("so far so good")
val two = new MyClass("foo") with MyTrait
two.getClass.getConstructor(classOf[String]) // NoSuchMethodException
}
}
编译后我们得到以下classes: App$$anon.class
, App$.class
, App.class
, MyClass.class
, MyTrait.class
.
MyClass.class
和 MyTrait.class
是微不足道的,我不会讨论它们。
让我们看看有趣的 classes:
App.class
这是 javap -c
输出:
public final class test.App {
public static void main(java.lang.String[]);
Code:
0: getstatic #16 // Field test/App$.MODULE$:Ltest/App$;
3: aload_0
4: invokevirtual #18 // Method test/App$.main:([Ljava/lang/String;)V
7: return
}
如我们所见,它只是调用了 App$.main
方法。
App$.class
这是里面的内容:
public final class test.App$ {
public static test.App$ MODULE$;
public static {};
Code:
0: new #2 // class test/App$
3: invokespecial #14 // Method "<init>":()V
6: return
public void main(java.lang.String[]);
Code:
0: new #19 // class test/MyClass
3: dup
4: ldc #21 // String foo
6: invokespecial #24 // Method test/MyClass."<init>":(Ljava/lang/String;)V
9: astore_2
10: aload_2
11: invokevirtual #28 // Method test/MyClass.getClass:()Ljava/lang/Class;
14: iconst_1
15: anewarray #30 // class java/lang/Class
18: dup
19: iconst_0
20: ldc #32 // class java/lang/String
22: aastore
23: invokevirtual #36 // Method java/lang/Class.getConstructor:([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
26: pop
27: getstatic #41 // Field scala/Predef$.MODULE$:Lscala/Predef$;
30: ldc #43 // String so far so good
32: invokevirtual #47 // Method scala/Predef$.println:(Ljava/lang/Object;)V
35: new #7 // class test/App$$anon
38: dup
39: invokespecial #48 // Method test/App$$anon."<init>":()V
42: astore_3
43: aload_3
44: invokevirtual #28 // Method test/MyClass.getClass:()Ljava/lang/Class;
47: iconst_1
48: anewarray #30 // class java/lang/Class
51: dup
52: iconst_0
53: ldc #32 // class java/lang/String
55: aastore
56: invokevirtual #36 // Method java/lang/Class.getConstructor:([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
59: pop
60: return
private test.App$();
Code:
0: aload_0
1: invokespecial #54 // Method java/lang/Object."<init>":()V
4: aload_0
5: putstatic #56 // Field MODULE$:Ltest/App$;
8: return
}
最有趣的部分是第 35 和 39 行。
第 35 行告诉我们,实际上我们创建了 App$$anon
class 的一个实例。它是一个自动生成的 class 代表 MyClass("foo") with MyTrait
.
第 39 行告诉我们如何初始化它。我们调用 App$$anon."<init>":()V
构造函数,但不向其传递任何值。现在我很好奇 "foo"
常量在哪里?
App$$anon.class
这是里面的内容:
public final class test.App$$anon extends test.MyClass implements test.MyTrait {
public test.App$$anon();
Code:
0: aload_0
1: ldc #16 // String foo
3: invokespecial #19 // Method test/MyClass."<init>":(Ljava/lang/String;)V
6: return
}
在这里我们终于看到了我们的 "foo"
(第 1 行)。 ldc
表示"load constant"; “#16”是对 table 中那个常量的引用;评论说它的类型是 String
并且值是 foo
.
第 3 行是对 MyClass."<init>":(Ljava/lang/String;)V
的调用。
TLDR 优化器在自动生成的 class 的构造函数中隐藏常量。所以构造函数本身没有任何参数。
PS:如果我们这样修改代码:
val foo = "foo"
val two = new MyClass(foo) with MyTrait
然后我们得到一个自动生成的 class 和一个接受 String
参数的构造函数:
public final class test.App$$anon extends test.MyClass implements test.MyTrait {
public test.App$$anon(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokespecial #17 // Method test/MyClass."<init>":(Ljava/lang/String;)V
5: return
}