1

I was trying out a Reloader class which should reload the class from disk when it's used. When something popped up that I didn't quite got a grasp on.

Which was that the static variable CHANGED in my Test would not update when I modified it through an instance of OtherClass created using the Reloader

So here is the code I used to test the case out.

Test.java:

import java.io.IOException;

public class Test {

    public static boolean CHANGED = false;
    static Test obj = new Test();

    static void change() {
        CHANGED = true;
        System.out.println(CHANGED); // [1] It is clearly changed to true.
    }

    public static void main(String[] args) {
        System.out.println(new OtherClass().toString2());
        System.out.println(CHANGED);
        try {
            Object foo = reload();
            System.out.println(foo);

        } catch (InstantiationException | IllegalAccessException | IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println(CHANGED); // [2] But here it is false again.
        }
    }

    public static Object reload() throws InstantiationException, IllegalAccessException, IOException {
        Object foo = new Reloader().loadClass("OtherClass").newInstance();
        return foo;
    }

}

Which calls the Reloader, which basically creates a new instance of a class reloading the changes from disk. And some more magic. Blatantly "stolen" for private use from: How to force Java to reload class upon instantiation?.

Reloader.java:

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Reloader extends ClassLoader {

    @Override
    public Class<?> loadClass(String s) {
        return findClass(s);
    }

    @Override
    public Class<?> findClass(String s) {
        try {
            byte[] bytes = loadClassData(s);
            return defineClass(s, bytes, 0, bytes.length);
        } catch (IOException ioe) {
            try {
                return super.loadClass(s);
            } catch (ClassNotFoundException ignore) {
            }
            ioe.printStackTrace(System.out);
            return null;
        }
    }

    private static byte[] loadClassData(String className) throws IOException {
        File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class");
        int size = (int) f.length();
        byte buff[] = new byte[size];
        try (FileInputStream fis = new FileInputStream(f)) {
            try (DataInputStream dis = new DataInputStream(fis)) {
                dis.readFully(buff);
            }
        }
        return buff;
    }
}

OtherClass.java

public class OtherClass {

    @Override
    public String toString() {
        Test.change();
        return "OtherClass";
    }

    public String toString2() {
        return "OLD";
    }
}

So in the Test.java file, there is 2 comments explaining what I expected to happen, but what actually happened. [1] It changed to true here, what was expected. [2] It was still false here, even though the code is definitely executed after [1], not expected.

My question then is, why or how did the value not change? Might it have to do that the class loader creates a whole new 'fake' new Test class that is derived from it or something?

EDIT: Forgot to show where it was invoked.

AgentM
  • 406
  • 4
  • 18
  • It's hard to say definitively since you aren't showing us any invocations of `change()`, but you are intentionally reloading with `Object foo = reload();` - why should `static` fields in classes that you reload persist changes? And what do you mean by "messing about with zipfiles, replacing code while it's running and all that jazz"? – Elliott Frisch Jun 11 '18 at 19:52
  • You're right, I forgot to put the OtherClass.java in there to show where it is invoked. (sorry for the messy code) – AgentM Jun 11 '18 at 19:54
  • This code gives exception to me. Is the same for you? – NiVeR Jun 11 '18 at 20:03
  • What exception does it throw? @NiVeR – AgentM Jun 11 '18 at 20:17
  • StackOverflowError – NiVeR Jun 11 '18 at 20:18
  • 1
    Don't use any packages... – AgentM Jun 11 '18 at 20:19

2 Answers2

1

Since the class is loaded by a different classloader hence it is considered a different class and it will have it's own values.

try not using different classloader and you will have CHANGED as true.

Sundeep
  • 452
  • 2
  • 12
  • Hmm, makes some sort of sense I guess. But then I'd need to find another way if at all possible to reload the class, without it being considered a different class. Any leads to how I could get there? – AgentM Jun 11 '18 at 21:16
1

are you trying to rewrite the .class files that are compiled and or load the definition from that? Probably not going to work well. From looking around you can't even use serializable as that is for instances.
Most likely you will need to create private static Boolean CHANGED = null; and store it's initial value in a file. then have

public static Boolean isChanged(){ 
    if (CHANGED == null){
        CHANGED = //read value from file
       return CHANGED;
   } else return CHANGED;
}



public static void setChanged(Boolean val){
    CHANGED = val;
    //write value to disk
}
mavriksc
  • 1,130
  • 1
  • 7
  • 10