【问题标题】:Using reflection to access outer class in Scala在 Scala 中使用反射访问外部类
【发布时间】:2011-09-06 19:37:10
【问题描述】:

在 Scala 中,是否可以使用反射来访问内部类的外部类?例如:

class A { 
   val inner = new { 
      println(getClass.getConstructors.toList)  
      println(getClass.getDeclaredFields.toList)
   }
}

scala> val a = new A
List(public $line11.$read$$iw$$iw$A$$anon$1($line11.$read$$iw$$iw$A))
List()
a: A = A@45f76fc7

我认为 Scala 编译器在某处保存了对外部类的引用,但您可以在这里看到构造函数中打印的字段列表是空的。此外,构造函数似乎引用了外部类实例(但很难确定——我不确定这里到底发生了什么)。我还注意到在某些情况下有一个 $outer 字段似乎是我需要的,但它并不总是存在,我不明白这一点。

为什么???!!! 我有一个内部类,我需要创建一个使用反射的新实例。新实例是从现有实例复制的,并且需要具有相同的外部引用。

【问题讨论】:

    标签: scala


    【解决方案1】:

    我认为你不能可靠地获得父级,除非你在内部类的构造函数之外使用父级。给定:

    class X  {
        val str = "xxyy"
        val self = this
    
        val inner = new {
            override def toString():String = {
                "inner " + self.toString
            }
    
        }
    
        override def toString():String = {
            "ima x" + this.str
        }
    
    }
    

    如果你使用 javap,你会得到一个私有字段 $outer

    但给定:

    class X  {
        val str = "xxyy"
        val self = this
    
        val inner = new {
            println(self)
    
        }
    
        override def toString():String = {
            "ima x" + this.str
        }
    
    }
    

    javap 没有指明 $outer 字段。

    【讨论】:

    • 你可以使用自我类型注解代替val self = this : class X { self => ... }
    【解决方案2】:

    如果你知道你需要这个,那么最好(到目前为止)明确引用并完全避免反射:

    class A { 
      outerA =>
      val inner = new { 
        val outer = outerA
        println(getClass.getConstructors.toList)  
        println(getClass.getDeclaredFields.toList)
      }
    }
    

    这里的神奇之处在于 self 类型 outerA => 允许您为 this 引用指定一个唯一的名称。

    【讨论】:

    • 问题是,我正在尝试创建一个对非程序员一样易于使用的 DSL,因此不能强制他们显式声明父引用。正如我在编辑中所说,我需要使用反射来复制内部类的实例。内部类的构造函数引用外部类(我假设这个 ctor 参数是由编译器添加的)。
    【解决方案3】:

    内部类必须有对其外部类的引用。这是由 JVM 隐式完成的。在以下示例中,对new InnerClass() 的调用被转换为new InnerClass(this)OuterClass 的实例存储在内部类中的字段$outer 中,可以使用此语法OuterClass.this 访问该字段。

    class OuterClass {
    
       val str = "abcde";
       val inner = new InnerClass();
    
       class InnerClass {
          def printStr() {
              println(OuterClass.this.str);
          }
       }
    }
    

    要通过反射构造新的 InnerClass 实例,您必须引用 OuterClass 的实例。要创建新实例,请执行以下操作。

    val outer = new OuterClass;
    
    val newInner = outer.inner.getClass.getDeclaredConstructor(classOf[OuterClass])
            .newInstance(outer);
    

    作为附录,我想说必须通过反射创建一个新的内部类听起来像是非常糟糕的编码实践。只有 OuterClass 才能看到 InnerClass 的实例,因此应该为可能需要创建 InnerClass 的新实例的任何情况做好准备。如果其他类具有 InnerClass 的可见性,则需要将其重构为自己的类。

    【讨论】:

    • 总的来说,我同意你的说法,并同意这会引发各种危险信号。然而,在这种情况下,为了获得我想要的最终用户 API,我愿意跳过丑陋的圈子。
    • 是的,我在发布后注意到您将其评论为 DSL。 $outer 不包含在反射字段中,因为它是大多数程序员不想真正了解的实现细节。你有你需要的吗?对于任何非静态内部类,构造函数都有一个附加参数,它是 Outer 类的一个实例。要访问内部字段,您必须通过外部类,因此提供参数应该不是问题。
    猜你喜欢
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-18
    • 2011-01-30
    相关资源
    最近更新 更多