从子类调用静态方法会导致IllegalAccessError

时间:2014-04-23 09:59:44

标签: java visibility

当我运行下面的源代码时,我在shell窗口中看到的运行时错误如下:

Exception in thread "main" java.lang.IllegalAccessError: tried to access method
pkgs.test.B.mStaPro()V from class pkgs.main.Main3
        at pkgs.main.Main3.m6(Main3.java:919)
        at pkgs.main.Main3.main(Main3.java:9)

字母V的含义是什么,在上面的mStaPro()之后看到了什么?

这是我的源代码,全部编译:

类Main3,包main:

package pkgs.main;
import pkgs.test.B;

class Main3 {
    static public void main(String args[]) {
        new Main3().m6();
    }

    void m6() {
        B.mStaPro();
    }
}

A类,包主:

package pkgs.main;

public class A {
    static protected void mStaPro() { System.out.println("A mStaPro()"); }
}

B类,包测试:

package pkgs.test;
import pkgs.main.A;

public class B extends A {
    // Note:  if this line below is commented out, then the runtime exception
    // mentioned in this post's title is not seen.
    static protected void mStaPro() { System.out.println("B mStaPro()"); }
}

以下是基于shell的编译和运行批处理文件的内容:

REM For compilation:
javac -Xlint -sourcepath ..\src -d ..\cls ..\src\pkgs\main\Main3.java


REM For running:
java -cp ..\cls pkgs.main.Main3

请注意我在B班内发表的评论。非常感谢任何评论。

修改

我尝试使用Apache Ant构建我的源代码,但获得的结果是相同的:

run:
     [java] Exception in thread "main" java.lang.IllegalAccessError: tried to access method pkgs.test.B.mStaPro()V from class pkgs.main.Main3
     [java]     at pkgs.main.Main3.m6(Main3.java:11)
     [java]     at pkgs.main.Main3.main(Main3.java:7)
     [java] Java Result: 1

main:

BUILD SUCCESSFUL

请注意,在本文顶部的第一个异常错误消息中,我在源代码中注释了很多代码,因此该错误消息与Ant上面的错误消息之间的行号不同。

我尝试过的另一件事是将JDK从版本1.7.0_40升级到版本1.7.0_55。

编辑2:

这是我的Apache Ant build.xml文件。它几乎与Apache Ant网站上提供的教程build.xml文件完全相同:

<project name="Main3 test" basedir="." default="main">

    <property name="src.dir"     value="src"/>

    <property name="build.dir"   value="build"/>
    <property name="classes.dir" value="${build.dir}/classes"/>
    <property name="jar.dir"     value="${build.dir}/jar"/>

    <property name="main-class"  value="pkgs.main.Main3"/>



    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" 
                includeantruntime="false" debug="true" 
        debuglevel="lines,vars,source" />
    </target>

    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <jar destfile="${jar.dir}/${ant.project.name}.jar" 
         basedir="${classes.dir}">
            <manifest>
                <attribute name="Main-Class" value="${main-class}"/>
            </manifest>
        </jar>
    </target>

    <target name="run" depends="jar">
        <java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/>
    </target>

    <target name="clean-build" depends="clean,jar"/>

    <target name="main" depends="clean,run"/>

</project>

4 个答案:

答案 0 :(得分:5)

Java类通常在三个阶段验证(粗略地说):

  1. 在编译时 - 这显然是在编译Java类时发生的。
  2. 当它加载到Java虚拟机时 - 当您第一次引用类时会发生这种情况。
  3. 在实际执行期间 - 当您第一次使用特定代码段时,会发生这种情况。
  4. Java编译器捕获了大多数编程错误。但是,pkgs.main.A#mStaPro方法的pkgs.test.B#mStaPro方法会欺骗Java编译器。以下是发生的事情:

    编译Main课程时,您正在static上调用mStaPro方法B。问题是,您是否正在调用pkgs.main.A类中定义的阴影方法或pkgs.test.B类中定义的隐藏方法?在这个问题中,Java编译器和Java运行时得出了不同的结论,这就是为什么Java编译器在Java运行时拒绝它时批准你的代码的原因:

    1. Java编译器认为您的代码合法,因为它认为您正在调用mStaPro中定义的A方法。 A类与Main位于同一个包中,因此其protected方法Main类为mStaPro类。
    2. Java运行时认为您的代码是非法的,因为它认为您正在调用B中定义的protected方法。此方法也是pkgs.test,但它在mStaPro包中定义。因此,不得从位于其他包中的Main调用此IllegalAccessError
    3. 为了通知您有关此非法代码的信息,Java运行时会抛出您遇到的Main#m。让我们在这里更进一步。如果查看生成的method shadowing (hiding)invokestatic #5 // Method pkgs/test/B.mStaPro:()V return 方法编译如下:

      B.mStaPro()
      return           // a void return statement is implicit in Java source code
      

      类似于您的Java源代码:

      B

      此编译结果与mStaPro类是否定义方法B无关。这是您遇到异常的原因。如果mStaPro类定义方法invokestatic,则B#mStaPro调用绑定到A#mStaPro方法(什么是非法的)。否则,调用绑定到A.mStaPro()(什么是合法的)。

      要解决此问题,您应该将实际目标类命名为B,而不是在static上调用该方法。但是,我必须诚实地说,我发现Java编译器的行为反直觉。 B.mStaPro()方法是静态的而非动态的。因此,Java编译器应该在编译时静态绑定pkgs/main/A.mStaPro:()VB的调用。实际上,没有这种假设,编译就不会成功。更好的是,当它遇到无法访问的A正在static中隐藏目标并且代码无法成功运行时,它应该只是产生编译错误。

      最后,在{{1}} {{1}}调用中可能会给出一个关于此行为的小提示,其中提到了这种行为:

        

      被调用的隐藏静态方法的版本取决于   是否从超类或子类调用它。

      然而,由于我上面提到的原因,这还不够好。但是,我多年来一直使用Java,因为阴影不是推荐的做法,我从来没有遇到过这个问题。像这样,你真的找到了一个边缘情况。

      Info :我将其发布到Java编译器邮件列表中。得到答案后,我会将信息添加到此帖子中。

答案 1 :(得分:1)

更改此行

static protected void mStaPro() { System.out.println("B mStaPro()"); }

 static public void mStaPro() { System.out.println("B mStaPro()"); }

javadocs-Controlling Access to Members of a Class

  

在成员级别,您也可以使用public修饰符或no修饰符(package-private),就像使用顶级类一样,并且具有相同的含义。对于成员,还有两个额外的访问修饰符:private和protected。 private修饰符指定只能在自己的类中访问该成员。 protected修饰符指定只能在自己的包中访问该成员(与package-private一样),此外,还可以在另一个包中通过其类的子类访问该成员。

答案 2 :(得分:0)

你的A和B类不在同一个包中,函数B.mStaPro()protected,它不能从其他包中访问,你应该将它们放在同一个包中或使其成为公共

答案 3 :(得分:0)

您无法访问其他包中的类(即,子类以外的类)中的一个包的受保护方法。