Scala:为什么扩展一个trait会破坏`Class.getConstructor()`?

时间:2017-11-09 05:42:06

标签: scala traits

当一个类扩展一个特征时,调用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

}

2 个答案:

答案 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.classApp$.classApp.classMyClass.classMyTrait.class

MyClass.classMyTrait.class是微不足道的,我不会讨论它们。

让我们看看有趣的课程:

  1. App.class
  2. 以下是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方法。

    1. App$.class
    2. 以下是内容:

      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"常数在哪里?

      1. App$$anon$1.class
      2. 以下是内容:

        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
        }