継承とインナークラスとクラスローダ
なんか、わけ分からないことに巻き込まれた。
まず継承関係のBaseとSubクラス。
abstract public class Base { protected void baseMethod() { System.err.println("Base.baseMethod()"); } abstract protected void perform(); } public class Sub extends Base { class Foo { void foo() { baseMethod(); // Sub.super.baseMethod(); // Sub.this.baseMethod(); } } public void perform() { new Foo().foo(); // baseMethod(); } }ちょっと変わっているのは、SubからはインナクラスであるFoo経由でBaseを呼び出している点。Subだけ、別のクラスローダでロードする。
import java.io.*;
class MyClassLoader extends ClassLoader {
MyClassLoader(ClassLoader parent) {
super (parent);
}
protected Class> findClass(String name) throws ClassNotFoundException {
File classFile = new File("subclasses/" + name.replace('.', '/') + ".class");
byte[] buf = new byte[(int)classFile.length()];
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(classFile));
int offset = 0;
int readSize = 0;
while (offset < buf.length) {
readSize = is.read(buf, offset, buf.length - offset);
if (readSize == -1)
throw new EOFException(classFile.getAbsolutePath());
offset += readSize;
}
return defineClass(name, buf, 0, buf.length);
}
catch (IOException ex) {
throw new ClassNotFoundException(name, ex);
}
finally {
if (is != null) {
try {is.close();}
catch (IOException ex) {ex.printStackTrace();}
}
}
}
}
そしてメインクラス。
public class Main {
public static void main(String[] args) throws Exception {
ClassLoader cl = new MyClassLoader(Main.class.getClassLoader());
Class> cls = cl.loadClass("Sub");
Base base = (Base)cls.newInstance();
base.perform();
}
}
baseclassesディレクトリとsubclassesディレクトリを用意して、コンパイル。
$ javac -d baseclasses Main.java Base.java MyClassLoader.java $ javac -d subclasses -cp baseclasses Sub.java
実行。
$ java -cp baseclasses Main
Exception in thread "main" java.lang.IllegalAccessError: tried to access method
Base.baseMethod()V from class Sub$Foo
at Sub$Foo.foo(Sub.java:4)
at Sub.perform(Sub.java:11)
at Main.main(Main.java:6)
妙なのは、Subの書き方を少し変えると大丈夫になる点(コメントで入っている)。インナクラスを使わずに、perform()から直接baseMethod()を呼ぶと、大丈夫。
public void perform() {
baseMethod();
}
インナクラスから、エンクロージングクラスを明示的に指定してsuper指定で呼び出せば大丈夫。
class Foo {
void foo() {
Sub.super.baseMethod();
}
}
でも、this指定だとダメ。
class Foo {
void foo() {
Sub.this.baseMethod();
}
}
たしかに、これらはバイトコードが変わっていて、ダメな場合は直接baseMethodがinvokestaticされているけど、大丈夫な場合は、access$001()が呼ばれ、そこ経由でbaseMethod()が呼ばれている。限りなくバグっぽいんだけど、これってこういうもんなんだろうか。環境:WindowsXP SP2 Java 1.6.0_04。ちなみに、各クラスのクラスローダを表示してみると、
public class Sub extends Base {
class Foo {
void foo() {
System.err.println("Foo :" + this.getClass());
System.err.println("Foo classloader:" + this.getClass().getClassLoader());
...
Sub.this.baseMethod();
}
}
public void perform() {
System.err.println("Sub :" + this.getClass());
System.err.println("Sub classloader:" + this.getClass().getClassLoader());
}
}
Sub :class Sub Sub classloader:MyClassLoader@1a758cb Foo :class Sub$Foo Foo classloader:MyClassLoader@1a758cb
同じものが使われている。





