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

use*_*290 2 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)
Run Code Online (Sandbox Code Playgroud)

在上面的mStaPro()之后看到字母V是什么意思?

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

class Main3,包main:

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

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

    void m6() {
        B.mStaPro();
    }
}
Run Code Online (Sandbox Code Playgroud)

A类,包主:

package pkgs.main;

public class A {
    static protected void mStaPro() { System.out.println("A mStaPro()"); }
}
Run Code Online (Sandbox Code Playgroud)

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()"); }
}
Run Code Online (Sandbox Code Playgroud)

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

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


REM For running:
java -cp ..\cls pkgs.main.Main3
Run Code Online (Sandbox Code Playgroud)

请注意我在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
Run Code Online (Sandbox Code Playgroud)

请注意,在本文顶部的第一个异常错误消息中,我在源代码中注释了很多代码,因此该错误消息和上面的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>
Run Code Online (Sandbox Code Playgroud)

Raf*_*ter 5

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

  1. 在编译时 - 这显然是在编译Java类时发生的.
  2. 当它加载到Java虚拟机时 - 当您第一次引用类时会发生这种情况.
  3. 在实际执行期间 - 当您第一次使用特定代码段时,会发生这种情况.

大多数编程错误都是由Java编译器捕获的.然而,Java编译器是由你的欺骗方法遮蔽(隐藏)中的pkgs.main.A#mStaPro该方法pkgs.test.B#mStaPro的方法.这是发生的事情:

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

  1. Java编译器认为您的代码合法,因为它认为您正在调用其中mStaPro定义的方法A.该A班住在同一个包Main,使得它的protected方法被认为是可见Main类.
  2. Java运行时认为您的代码是非法的,因为它认为您正在调用其中mStaPro定义的方法B.此方法也是如此,protected但它在pkgs.test包中定义.因此,mStaPro不得Main在不同的包中生活.

为了通知您有关此非法代码的信息,Java运行时会抛出IllegalAccessError您遇到的内容.让我们在这里更进一步.如果查看生成的Java字节代码,该Main#m方法编译如下:

invokestatic  #5  // Method pkgs/test/B.mStaPro:()V
return        
Run Code Online (Sandbox Code Playgroud)

什么类似于您的Java源代码:

B.mStaPro()
return           // a void return statement is implicit in Java source code
Run Code Online (Sandbox Code Playgroud)

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

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

最后,这种行为未成年人暗示可能会在Oracle的给予教程static调用其中提到此行为:

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

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

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