Java中关于私有静态嵌套类的合成访问器的Eclipse警告?

51

我的同事建议使Eclipse的几个代码格式化和警告设置更加严格。其中大部分更改都是有道理的,但我在Java中遇到了一个奇怪的警告。以下是一些测试代码以复现这个“问题”:

package com.example.bugs;

public class WeirdInnerClassJavaWarning {
    private static class InnerClass
    {
        public void doSomething() {}
    }

    final private InnerClass anInstance;

    {
        this.anInstance = new InnerClass();   // !!!
        this.anInstance.doSomething();
    }
}
// using "this.anInstance" instead of "anInstance" prevents another warning,
// Unqualified access to the field WeirdInnerClassJavaWarning.anInstance    

在我的新警告设置下,带有 !!! 的行在 Eclipse 中会给我这个警告:

访问封闭构造函数 WeirdInnerClassJavaWarning.InnerClass() 是由合成的访问器方法进行模拟的。增加其可见性将提高性能。

这是什么意思?当我将 "private static class" 更改为 "protected static class" 时,警告消失了,但对我来说毫无意义。


编辑: 我最终找到了 "正确" 的解决方法。真正的问题似乎是这个嵌套的私有静态类缺少一个公共构造函数。那个微调消除了警告:

package com.example.bugs;

public class WeirdInnerClassJavaWarning {
    private static class InnerClass
    {
        public void doSomething() {}
        public InnerClass() {}
    }

    final private InnerClass anInstance;

    {
        this.anInstance = new InnerClass();
        this.anInstance.doSomething();
    }
}

我想将该类作为私有嵌套类(这样其它的类就无法访问它,包括封闭类的子类),并且我想将其定义为静态类。

我仍然不明白为什么将嵌套类声明为protected而不是private是解决“问题”的另一种方法,但也许这是Eclipse的一个怪异/错误之处。

(抱歉,我应该将其称为NestedClass而不是InnerClass以使其更加清晰。)


我不明白一个点:拥有一个不能被其他人使用的内部类有什么用处?我想把这个类放在一个单独的类文件中,让其他程序员选择它是否对其他范围有用。 - Marco Sulla
6个回答

46
你可以按照以下步骤消除警告:

package com.example.bugs;

public class WeirdInnerClassJavaWarning {
    private static class InnerClass {
        protected InnerClass() {}  // This constructor makes the warning go away
        public void doSomething() {}
    }

    final private InnerClass anInstance;
    {
        this.anInstance = new InnerClass(); 
        this.anInstance.doSomething();
    }
}

正如其他人所说,Eclipse抱怨因为一个没有显式构造函数的私有类不能从外部实例化,除非通过Java编译器创建的合成方法。如果你拿到代码,编译它,然后用jad(*)反编译,你会得到以下结果(重新格式化):

public class Test {
  private static class InnerClass {
    public void doSomething() {}
    // DEFAULT CONSTRUCTOR GENERATED BY COMPILER:
    private InnerClass() {}

    // SYNTHETIC METHOD GENERATED BY THE JAVA COMPILER:    
    InnerClass(InnerClass innerclass) {
      this();
    }
  }

  public Test() {
    anInstance.doSomething();
  }

  // Your instance initialization as modified by the compiler:
  private final InnerClass anInstance = new InnerClass(null);
}

如果您添加了一个受保护的构造函数,则不需要合成代码。理论上,我想,与使用公共或受保护的构造函数的非合成代码相比,合成代码稍微慢一些。(*)对于jad,我链接到了维基百科页面...托管此程序的域名已过期,但维基百科链接到另一个我自己没有测试过的域名。我知道还有其他(可能更近期的)反编译器,但这是我开始使用的反编译器。注意:它在反编译最近的Java类文件时会“抱怨”,但它仍然做得很好。

5
最佳方案,除非这是一个J2ME项目,否则是在Eclipse中禁用警告并编写符合您意图的代码。在JIT之后,性能损失将为零或可忽略不计。 - Darron
1
@Darron先生与公司,我并不清楚性能应该从哪里来。编译器会生成合成方法,因此在运行时,它应该看起来就像您自己放置的一样。或者我漏掉了什么吗? - Jens Schauder
只需使用“javac -d tmp -XD-printflat WeirdInnerClassJavaWarning.java”进行编译,即可查看合成代码,无需使用jad。 - Joseph Lust
这个警告有任何安全方面的影响吗? - Mr_and_Mrs_D
我不知道任何安全方面的影响。 - Eddie
显示剩余5条评论

21

顺便提一下,关闭警告的设置在Java错误/警告页面的“代码风格”下,称为:

访问封闭类型的不可访问成员


1
那对我来说已经设置为忽略了,但我仍然看到警告。也许Juno中有些东西已经改变了。 - Duncan Jones
在项目特定的设置中,它是否被设置为警告? - robinst
我知道这听起来很可笑,但是如果警告仍然存在,请在某个地方添加一些字符并保存文件。之后警告就会消失了。 - phil294
这个警告在 Kepler 中可以被忽略。 - Adrian Kaluzinski

9

从WeirdInnerClassJavaWarning中无法实例化InnerClass。它是私有的,JVM不会允许你这样做,但是Java语言(出于某种原因)会。

因此,javac会在InnerClass中创建一个额外的方法,该方法只会返回new InnerClass(),从而允许您从WeirdInnerClassJavaWarning创建InnerClass实例。

我认为您真的不需要将其删除,因为性能下降会非常微小。但是,如果您真的想这样做,也可以。


2
+1 在现代JVM上,性能提升应该完全可以忽略不计。Eclipse只是过于热情了,我会完全关闭那个警告。 - skaffman
无法从WeirdInnerClassJavaWarning实例化InnerClass。它是私有的,JVM不允许你这样做,但出于某种原因,Java语言会允许。如果没有构造函数存在,Java语言会自动创建一个无参公共构造函数。Eclipse警告的是这种魔法的运作方式。 - Powerlord
1
如果是私有内部类,我猜这个构造函数会是私有的。否则,这个警告就没有任何意义了。 - alamar

4
我仍然不明白为什么将嵌套类设为protected而不是private是解决“问题”的另一种方法,但也许这是Eclipse的怪癖/错误。
那不是Eclipse的怪癖/错误,只是Java的一个特性。《Java语言规范8.8.9》说:
如果类被声明为protected,则默认构造函数隐式地获得了protected访问修饰符...

...而且对于封装类,protected和private有不同的含义吗?我认为唯一的区别是如何授予子类访问权限。 - Jason S
1
它在源代码级别(编译器)没有任何影响,但在字节码级别(虚拟机)有影响。据我所知,JVM不知道嵌套类,这就是为什么您会为它们获得额外的.class文件。编译器必须创建合成方法,以便“外部”类可以访问嵌套类的私有成员(在运行时)。 (顺便说一句:protected还包括包访问权限,这足以避免警告) (顺便提一句:根据JLS的定义,静态嵌套类不是内部类...) - user85421

3
为了帮助大家,以下是使用原始的类代码进行翻译:

如果您使用问题中的原始类代码,则会得到以下结果:

javac -XD-printflat WeirdInnerClassJavaWarning.java -d tmp

原始输出,编译器添加了注释。请注意合成的包私有类和构造函数的添加。

public class WeirdInnerClassJavaWarning {
    {
    }

    public WeirdInnerClassJavaWarning() {
        super();
    }
    {
    }
    private final WeirdInnerClassJavaWarning$InnerClass anInstance;
    {
        this.anInstance = new WeirdInnerClassJavaWarning$InnerClass(null);
        this.anInstance.doSomething();
    }
}

class WeirdInnerClassJavaWarning$InnerClass {

    /*synthetic*/ WeirdInnerClassJavaWarning$InnerClass(WeirdInnerClassJavaWarning$1 x0) {
        this();
    }

    private WeirdInnerClassJavaWarning$InnerClass() {
        super();
    }

    public void doSomething() {
    }
}

/*synthetic*/ class WeirdInnerClassJavaWarning$1 {
}

0

你可以通过使用默认作用域而不是私有或受保护的作用域来摆脱它,例如:

static class InnerClass ...

值得一提的是,当你把鼠标光标放在代码警告行上并按下ctrl-1时,Eclipse可能会自动为您修复此问题。

谢谢,但我不想要那个。它应该对于除了封闭类之外的其他类是不可见的。 - Jason S
1
在这种情况下,您可能希望抑制警告,因为Eclipse的面向性能的警告与您的需求无关。 - izb
相关的配置是“访问封闭类型的不可访问成员”。 - akauppi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接