Search code examples
javainheritancepolymorphismclassloader

Overriding default accessor method across different classloaders breaks polymorphism


I come across to a strange behavior while trying to override a method with default accessor (ex: void run()). According to Java spec, a class can use or override default members of base class if classes belongs to the same package. Everything works correctly while all classes loaded from the same classloader. But if I try to load a subclass from separate classloader then polymorphism don't work.

Here is sample:

App.java:

import java.net.*;
import java.lang.reflect.Method;

public class App {
    public static class Base {
        void run() {
            System.out.println("error");
        }
    }
    public static class Inside extends Base {
        @Override
        void run() {
            System.out.println("ok. inside");
        }
    }
    public static void main(String[] args) throws Exception {
        {
            Base p = (Base) Class.forName(Inside.class.getName()).newInstance();
            System.out.println(p.getClass());
            p.run();
        } {
            // path to Outside.class
            URL[] url = { new URL("file:/home/mart/workspace6/test2/bin/") };
            URLClassLoader ucl = URLClassLoader.newInstance(url);
            final Base p = (Base) ucl.loadClass("Outside").newInstance();
            System.out.println(p.getClass());
            p.run();
            // try reflection
            Method m = p.getClass().getDeclaredMethod("run");
            m.setAccessible(true);
            m.invoke(p);
        }
    }
}

Outside.java: should be in separate folder. otherwise classloader will be the same

public class Outside extends App.Base {
    @Override
    void run() {
        System.out.println("ok. outside");
    }
}

The output:

class App$Inside
ok. inside
class Outside
error
ok. outside

So then I call Outside#run() I got Base#run() ("error" in output). Reflections works correctly.

Whats wrong? Or is it expected behavior? Can I go around this problem somehow?


Solution

  • From Java Virtual Machine Specification:

    5.3 Creation and Loading
    ...
    At run time, a class or interface is determined not by its name alone, but by a pair: its fully qualified name and its defining class loader. Each such class or interface belongs to a single runtime package. The runtime package of a class or interface is determined by the package name and defining class loader of the class or interface.


    5.4.4 Access Control
    ...
    A field or method R is accessible to a class or interface D if and only if any of the following conditions is true:

    • ...
    • R is either protected or package private (that is, neither public nor protected nor private), and is declared by a class in the same runtime package as D.