<< 2008/01/23 | Home | 2008/01/25 >>
PR: 転職    転職    合宿免許    漫画    シルバー    ブライダルエステ    墓地・霊園    葬式   

継承とインナークラスとクラスローダ

なんか、わけ分からないことに巻き込まれた。

まず継承関係の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

同じものが使われている。

このサイトの掲載内容は私自身の見解であり、必ずしもIBMの立場、戦略、意見を代表するものではありません。
日本アイ・ビー・エム 花井 志生 Since 1997.6.8