当一个类扩展一个特征时,调用getConstructor()
否则会成功然后抛出一个NoSuchMethodException
。为什么会发生这种情况以及可以采取哪些措施呢?
在下面的代码中,getConstructor()
的第一次调用会返回而不会出错。第二个提出异常。
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
}
答案 0 :(得分:2)
new MyClass("foo") with MyTrait
使用零参数构造函数创建一个新的匿名类(此构造函数在内部执行super("foo")
),然后创建该新类的实例。
classOf[MyClass] eq one.getClass
classOf[MyClass] ne two.getClass
由于新匿名类中没有基于String
的构造函数,因此代码失败。
答案 1 :(得分:1)
让我们仔细看看会发生什么。
我略微更改了您的定义以简化生成的输出。我的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
}
}
编译完成后,我们将获得以下课程:App$$anon$1.class
,App$.class
,App.class
,MyClass.class
,MyTrait.class
。
MyClass.class
和MyTrait.class
是微不足道的,我不会讨论它们。
让我们看看有趣的课程:
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$1
38: dup
39: invokespecial #48 // Method test/App$$anon$1."<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$1
类的实例。它是一个自动生成的类,代表MyClass("foo") with MyTrait
。
第39行告诉我们如何初始化它。我们调用App$$anon$1."<init>":()V
构造函数,但不传递任何值。现在我好奇"foo"
常数在哪里?
App$$anon$1.class
以下是内容:
public final class test.App$$anon$1 extends test.MyClass implements test.MyTrait {
public test.App$$anon$1();
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
表示&#34;加载常数&#34 ;; &#34;#16&#34;是表中对该常量的引用;评论说其类型为String
,值为foo
。
第3行是MyClass."<init>":(Ljava/lang/String;)V
的调用。
TLDR 优化器会在自动生成的类的构造函数中隐藏常量。所以构造函数本身没有任何参数。
PS :如果我们改变我们的代码:
val foo = "foo"
val two = new MyClass(foo) with MyTrait
然后我们得到一个自动生成的类,其中包含一个接受String
参数的构造函数:
public final class test.App$$anon$1 extends test.MyClass implements test.MyTrait {
public test.App$$anon$1(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokespecial #17 // Method test/MyClass."<init>":(Ljava/lang/String;)V
5: return
}