合成访问器方法警告

时间:2010-12-21 16:11:49

标签: java eclipse performance java-synthetic-methods

我在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");
    }
}

**行给出了同样的警告。

5 个答案:

答案 0 :(得分:24)

什么是“合成”方法?

Method类(及其父级Member)开始,我们了解到合成成员是“由编译器引入”,而JLS §13.1会告诉我们更多。它指出:

  

如果Java编译器发出的构造与源代码中显式或隐式声明的构造不对应,则必须将其标记为 synthetic

由于本节讨论二进制兼容性,JVMS也值得参考,JVMS §4.7.8增加了一些上下文:

  

必须使用Synthetic属性标记未出现在源代码中的类成员,否则必须设置其ACC_SYNTHETIC标志。此要求的唯一例外是编译器生成的方法,这些方法不被视为实现工件....

     

JDK 1.1中引入了Synthetic属性以支持嵌套类和接口。

换句话说,“合成”方法是Java编译器引入的实现工件,以支持JVM本身不支持的语言功能。

有什么问题?

你遇到了一个这样的情况;您正试图从匿名内部类访问类的private字段。 Java语言允许这样做,但JVM不支持它,因此Java编译器生成一个合成方法,将private字段暴露给内部类。这是安全的,因为编译器不允许任何其他类调用此方法,但它确实引入了两个(小)问题:

  1. 正在宣布其他方法。对于绝大多数用例来说,这不应该是一个问题,但是如果你在像Android 这样的受限环境中工作,会产生很多这些合成方法,那么你可能会遇到问题。 / LI>
  2. 通过合成方法间接访问此字段,而不是直接访问。除了对性能敏感的用例外,这也不应成为问题。如果由于性能原因你不想在这里使用getter方法,那么你也不需要合成的getter方法。这在实践中很少成为问题。
  3. 简而言之,他们真的不错。除非你有一个具体的理由要避免使用合成方法(即你已经确定它们是你的应用程序中的瓶颈),你应该让编译器按照它认为合适的方式生成它们。如果它会打扰你,请考虑关闭Eclipse警告。

    我该怎么办?

    如果你真的想阻止编译器生成合成方法,你有几个选择:

    选项1:更改权限

    可以直接访问内部类的包 - 私有或protected字段。特别是像Swing应用程序这样的东西应该没问题。但是你说你想避免这种情况,所以我们就去了。

    选项2:创建一个getter

    保持字段不变,但明确创建protectedpublic 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稍后可以更改为指向其他对象,则需要再次重建ActionListener(虽然这也很明确,所以可以说这是一个功能),但我认为它是最清楚地表明其意图的选择。


    除了线程安全

    您的示例Test类不是线程安全的 - testString是在单独的Thread中设置的,但您没有在该分配上进行同步。将testString标记为volatile足以确保所有线程都能看到更新。 Synthetic示例没有此问题,因为testButton仅在构造函数中设置,但由于这种情况,建议将testButton标记为final。< / p>

答案 1 :(得分:3)

在您的第二个示例中,没有必要直接访问testButton;您可以通过检索动作事件的来源来访问它。

对于resetButton()方法,您可以添加一个参数来传递对象以进行操作,如果您已经这样做,那么降低其访问限制并不是一个大问题。

答案 2 :(得分:0)

鉴于上下文(作为相当昂贵的操作的一部分分配给变量一次),我认为你不需要做任何事情。

答案 3 :(得分:0)

我认为问题是你在父类上设置了一个String。这将在性能上受到影响,因为线程需要查找以再次查看该变量的位置。我认为更简洁的方法是使用Callable返回一个String,然后执行.get()或返回结果的东西。获得结果后,您可以将数据重新设置为父类。

这个想法是你想确保Thread只做一件事而且只做一件事,而不是在其他类上设置变量。这是一种更简洁的方法,可能更快,因为内部线程不会访问自身之外的任何内容。这意味着锁定更少。 :)

答案 4 :(得分:0)

这是Java的默认可见性(也称为&#34;包私有&#34;)的极少数情况之一。

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方法。所以它基本上做同样的事情,除了额外方法的最小开销。