私有字段的Scala名称处理和JavaFX FXML注入 [英] Scala name mangling of private fields and JavaFX FXML injection
问题描述
以下示例和说明很长,所以这是我的问题的要点:当使用坚持执行字段注入的框架(在实际上应该保持私有的字段上)时,如何处理scalac对私有字段的名称处理)?
The following example and explanations are quite long, so here is the gist of my question: how to deal with scalac's name-mangling of private fields when using a framework which insists on performing field injection (on fields which really should stay private)?
我正在使用ScalaFX/JavaFX和FXML在Scala中编写应用程序.当您使用FXML在JavaFX中定义视图时,FXML中定义的对象(例如按钮和文本字段)将通过以下方式注入到控制器中:
I am writing an application in Scala, using ScalaFX/JavaFX and FXML. When you use FXML to define your views in JavaFX, objects defined in FXML (such as buttons and text fields) are injected into the controller by :
- 向FXML元素添加
fx:id
属性 - 使用
@FXML
批注和与FXML中定义的fx:id
属性值匹配的字段名称向控制器添加(通常是私有的)字段 -
FXMLoader
实例化控制器时,它会通过反射自动将fx:id
注释的元素注入到控制器的匹配@FXML
注释的字段中
- adding an
fx:id
property to the FXML elements - adding (usually private) fields to the controller, with the
@FXML
annotation and with field names matching the values of thefx:id
properties defined in the FXML - when the
FXMLoader
instantiates the controller, it automatically injects thefx:id
annotated elements into the matching@FXML
annotated fields of the controller through reflexion
我不是字段注入的忠实拥护者,但这就是FXML的工作方式.但是,由于在某些情况下编译器执行了字段名处理,因此我在Scala中遇到了意想不到的麻烦...
I'm not a big fan of field injection, but that's how FXML works. However, I've run into unexpected complications in Scala, due to field name mangling performed by the compiler in some circumstances...
这是一个示例应用程序:
Here is an example application :
test/TestApp.scala(没什么有趣的,只需要运行示例)
test/TestApp.scala (nothing interesting, just needed to run the example)
package test
import javafx.application.Application
import javafx.fxml.FXMLLoader
import javafx.scene.{Scene, Parent}
import javafx.stage.Stage
object TestApp {
def main(args: Array[String]) {
Application.launch(classOf[TestApp], args: _*)
}
}
class TestApp extends Application {
override def start(primaryStage: Stage): Unit = {
val root: Parent = FXMLLoader.load(getClass.getResource("/test.fxml"))
val scene: Scene = new Scene(root, 200, 200)
primaryStage.setTitle("Test")
primaryStage.setScene(scene)
primaryStage.show()
}
}
test.fxml(视图)
test.fxml (the view)
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="test.TestController">
<children>
<CheckBox fx:id="testCheckBox" mnemonicParsing="false" text="CheckBox"/>
<Button fx:id="testButton" mnemonicParsing="false" text="Button"/>
</children>
</VBox>
test/TestController.scala(用于test.fxml视图的控制器)
test/TestController.scala (the controller for the test.fxml view)
package test
import javafx.fxml.FXML
import javafx.scene.{control => jfxsc}
import scalafx.Includes._
class TestController {
@FXML private var testCheckBox: jfxsc.CheckBox = _
@FXML private var testButton: jfxsc.Button = _
def initialize(): Unit = {
println(s"testCheckBox=$testCheckBox")
println(s"testButton=$testButton")
testCheckBox.selected.onChange {
testButton.text = "changed"
}
}
}
运行应用程序时,println
语句显示testCheckBox
被正确注入,但是testButton
为空.如果我单击该复选框,则在调用testButton.text_=
时,正如预期的那样,会有一个NullPointerException
.
When running the application, the println
statements show that testCheckBox
gets injected properly, but testButton
is null. If I click on the checkbox, there is, as expected, a NullPointerException
when calling testButton.text_=
.
查看已编译的类时,原因很明显:
The reason is quite obvious when looking at the compiled classes :
- 有一个
TestController$$anonfun$initialize$1
类,用于通过initialize()
方法传递给testCheckBox.selected.onChange()
的匿名函数 - 在
TestController
类中,有两个私有字段:testCheckBox
(按预期)和test$TestController$$testButton
(而不只是testButton
),以及访问器/更改器方法.其中,只有test$TestController$$testButton
的访问器方法是公开的.
- There is a
TestController$$anonfun$initialize$1
class, for the anonymous function passed totestCheckBox.selected.onChange()
in theinitialize()
method - In the
TestController
class, there are two private fields :testCheckBox
(as expected) andtest$TestController$$testButton
(rather than justtestButton
), and the accessor/mutator methods. Of those, only the accessor method fortest$TestController$$testButton
is public.
很明显,Scala编译器修改了testButton
字段的名称,因为它必须公开其访问器方法(以便从TestController$$anonfun$initialize$1
访问它),并且因为该字段和accesor/mutator方法应保持相同的名称
Clearly, the Scala compiler mangled the name of the testButton
field because it had to make its accessor method public (to access it from TestController$$anonfun$initialize$1
) and because the field and the accesor/mutator methods should keep the same name.
现在,最后,这是我的问题:是否有合理的解决方案来应对这种情况?现在,我所做的就是将字段设为公开:由于编译器不需要更改其可见性,因此不会破坏其名称.但是,这些领域确实没有公开的业务.
Now, finally, here is my question: is there a reasonable solution to deal with this situation? Right now, what I have done is make the fields public: since the compiler doesn't need to change their visibility, it won't mangle their name. However, those fields really have no business being public.
注意:另一个解决方案是使用scala-fxml库,该库完全隐藏字段注入,但是出于其他原因,我宁愿使用bog-standard FXML加载.
Note: Another solution would be to use the scala-fxml library, which completely hides the field injection, but I'd rather use bog-standard FXML loading for other reasons.
推荐答案
这是我声明注入字段的方式:
This is how I declare my injected fields:
@FXML
var popoutButton: Button = _
IOW,放弃private
,一切正常.如果您来自Java世界,感觉会有些脏,但是解决方法需要更多的代码.
IOW, leave off the private
and things work fine. Feels a bit dirty if you're coming from the Java world, but workarounds take a lot more code.
由fx:include
暗示的控制器也可以正常工作:
Controllers implied by fx:include
also work fine:
FXML :
<fx:include fx:id="paramTable" source="ParameterTable.fxml" />
Scala :
@FXML
var paramTable: TableView[(ModelParameter, ParameterValue)] = _
@FXML
var paramTableController: ParameterTableController = _
这篇关于私有字段的Scala名称处理和JavaFX FXML注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!