【问题标题】:Synthetic accessor method warning合成访问器方法警告
【发布时间】:2011-05-28 22:19:20
【问题描述】:

我在 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 的访问修饰符。 另外,不要为它创建一个吸气剂。

应该做哪些改变?


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

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

【问题讨论】:

  • 在网站上搜索,有很多关于这个警告的帖子。

标签: java eclipse performance java-synthetic-methods


【解决方案1】:

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

【讨论】:

  • 这只是一个例子。我没有执行此操作。
  • 如果你做了很多次,现代 JVM 会内联它。
【解决方案2】:

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

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

【讨论】:

  • 感谢阿米尔。但这只是一个例子。将编辑我的问题,这将更具描述性
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    什么是“合成”方法?

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

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

    由于本节讨论的是二进制兼容性,因此 JVMS 也值得参考,JVMS §4.7.8 添加了更多上下文:

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

    Synthetic 属性是在 JDK 1.1 中引入的,用于支持嵌套类和接口。

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

    有什么问题?

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

    1. 正在声明其他方法。对于绝大多数用例来说,这应该不是问题,但如果您在 Android 等受限环境中工作并且正在生成大量此类合成方法,您可能会遇到问题。
    2. 通过合成方法间接访问该字段,而不是直接访问。除了对性能高度敏感的用例之外,这也不应该是一个问题。如果出于性能原因您不想在这里使用 getter 方法,那么您也不需要合成 getter 方法。这在实践中很少成为问题。

    简而言之,它们确实不错。除非您有具体的理由避免使用合成方法(即您已经确定它们是应用程序中的瓶颈),否则您应该让编译器按照它认为合适的方式生成它们。如果 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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2014-06-11
        • 2010-09-24
        • 2020-06-28
        • 2015-05-22
        • 2010-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多