合成访问器方法警告
我在eclipse中做了一些新的警告设置。 有了这些新设置,我面临一个奇怪的警告。 阅读后我知道它是什么,但找不到删除它的方法。
这是我的示例代码问题
public class Test { private String testString; public void performAction() { new Thread( new Runnable() { @Override public void run() { testString = "initialize"; // ** } }); } }
带* *的行在eclipse中给我一个警告
Read access to enclosing field Test.testString is emulated by a synthetic accessor method. Increasing its visibility will improve your performance.
问题是,我不想更改testString
的访问修饰符。 此外,不想为它创建一个getter。
应该做些什么改变?
More descriptive example public class Synthetic { private JButton testButton; public Synthetic() { testButton = new JButton("Run"); testButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { /* Sample code */ if( testButton.getText().equals("Pause") ) { resetButton(); // ** } else if( testButton.getText().equals("Run") ) { testButton.setText("Pause"); // ** } } } ); } public void reset() { //Some operations resetButton(); } private void resetButton() { testButton.setText("Run"); } }
带**
行给了我同样的警告。
在第二个例子中,没有必要直接访问testButton
; 您可以通过检索动作事件的来源来访问它。
对于resetButton()
方法,您可以添加一个参数来传递对象以进行操作,如果您已经这样做,那么降低其访问限制并不是一个大问题。
什么是“合成”方法?
从Method
类(和它的父类, Member
)开始,我们了解到合成成员是“ 由编译器引入的 ”,而JLS§13.1将告诉我们更多。 它指出:
如果Java编译器发出的构造与源代码中显式或隐式声明的构造不对应,则必须将其标记为合成构造
由于本节讨论的是二进制兼容性,因此JVMS也值得参考,而JVMS§4.7.8增加了更多的上下文:
必须使用
Synthetic
属性标记未出现在源代码中的类成员,否则必须设置其ACC_SYNTHETIC
标志。 此要求的唯一例外是编译器生成的方法,这些方法不被视为实现工件….在JDK 1.1中引入了
Synthetic
属性以支持嵌套类和接口。
换句话说,“合成”方法是Java编译器引入的实现工件,以便支持JVM本身不支持的语言function。
有什么问题?
你遇到了一个这样的情况; 您正试图从匿名内部类访问类的private
字段。 Java语言允许这样做,但JVM不支持它,因此Java编译器生成一个合成方法,将private
字段暴露给内部类。 这是安全的,因为编译器不允许任何其他类调用此方法,但它确实引入了两个(小)问题:
- 正在宣布其他方法。 对于绝大多数用例而言,这应该不是问题,但如果您在像Android这样的受限环境中工作并且正在生成大量这些合成方法,则可能会遇到问题。
- 访问此字段是通过合成方法间接完成的,而不是直接访问。 除了对性能敏感的用例外,这也不应成为问题。 如果你出于性能原因不想在这里使用getter方法,那么你也不需要合成的getter方法。 这在实践中很少成为问题。
简而言之,他们真的不错。 除非你有一个具体的理由要避免合成方法(即你已经确定它们是你的应用程序中的瓶颈),你应该让编译器按照它认为合适的方式生成它们。 如果它会打扰你,请考虑关闭Eclipse警告。
我该怎么办?
如果你真的想阻止编译器生成合成方法,你有几个选择:
选项1:更改权限
内部类可直接访问包私有或protected
字段。 特别是像Swing应用程序这样的东西应该没问题。 但是你说你想避免这种情况,所以我们走了。
选项2:创建一个getter
保持字段不变,但显式创建protected
或public
getter,并使用它。 这基本上是编译器自动为您做的事情,但现在您可以直接控制方法的行为。
选项3:使用局部变量并与两个类共享引用
这是更多的代码,但它是我个人的最爱,因为你正在明确内部和外部类之间的关系。
public Synthetic() { // Create final local instance - will be reachable by the inner class final JButton testButton = new JButton("Run"); testButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { /* Sample code */ if( testButton.getText().equals("Pause") ) { resetButton(); } else if( testButton.getText().equals("Run") ) { testButton.setText("Pause"); } } }); // associate same instance with outer class - this.testButton can be final too this.testButton = testButton; }
这并不总是你想要做的事情。 例如,如果testButton
可以更改为指向另一个对象,则需要再次重新testButton
ActionListener
(虽然这也很明确,所以可以说这是一个特性),但我认为它是最清楚地certificate它的选项意图。
除了线程安全
您的示例Test
类不是线程安全的 – testString
是在单独的Thread
设置的,但您没有在该分配上进行同步。 将testString
标记为volatile
将足以确保所有线程都看到更新。 Synthetic
示例没有这个问题,因为testButton
只在构造函数中设置,但是因为在这种情况下,建议将testButton
标记为final
。
鉴于上下文(您作为一个相当昂贵的操作的一部分分配给变量一次),我认为您不需要做任何事情。
我认为问题是你在父类上设置一个String。 这将在性能上受到影响,因为线程需要查找以再次查看该变量的位置。 我认为更简洁的方法是使用Callable返回一个String,然后执行.get()或返回结果的东西。 获得结果后,您可以将数据重新设置为父类。
这个想法是你想确保Thread只做一件事,只做一件事,而不是在其他类上设置变量。 这是一种更简洁的方法,可能更快,因为内部线程不会访问自身之外的任何内容。 这意味着锁定更少。 🙂
这是Java的默认可见性(也称为“包私有”)的罕见情况之一。
public class Test { /* no private */ String testString; public void performAction() { new Thread( new Runnable() { @Override public void run() { testString = "initialize"; // ** } }); } }
这将执行以下操作:
-
testString
现在可用于与外部类(Test
)相同的包中的所有类。 - 由于内部类实际上是作为
OuterClassPackage.OuterClassName$InnerClassName
生成的,因此它们也位于同一个包中。 因此,他们可以直接访问此字段。 - 与使该字段
protected
相反,默认可见性不会使该字段可用于子类(当然它们在同一个包中时除外)。 因此,您不会污染外部用户的API。
当使用private
,javac会生成一个合成访问器 ,它本身只是一个带有Java默认可见性的getter方法。 所以它基本上做同样的事情,除了额外的方法的最小开销。