0

Is it possible to forbid inlining of final Strings? Probably that is a weird requirement but I need to change final field value in runtime. It is necessary for unit testing.

For example:

class asd {
    public static final String value = "sdfmsdkofl";

    public String getValue() {
        return value;
    }
}

I need somehow to avoid inlining of value variable. And I can not change source code :(. That means I can not change way of field is accessed, add getters and setters.

Some magical parameters that would switch off ALL optimisations, even such simple.

michael nesterenko
  • 14,222
  • 25
  • 114
  • 182
  • 4
    The ideal solution here is to reorganize your code so it can be tested without this sort of madness. – Louis Wasserman Feb 28 '12 at 19:06
  • 3
    did you check http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection – VirtualTroll Feb 28 '12 at 19:08
  • @Amine is right. I it is exact duplicate to http://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection – AlexR Feb 28 '12 at 19:18
  • For the special case of compile-time constant `String`, the techniques discussed at that question won't work, as discussed in the caveats to that question. – Louis Wasserman Feb 28 '12 at 19:22
  • 1
    Are you sure it's necessary? - if you explain why you need to do this, and show more detail, then perhaps some other solutions can be suggested... – DNA Feb 28 '12 at 19:22
  • @DNA, I know there is a vast majority of other ways to solve this issue. However I want to get an answer if it is possible to avoid inlining final immutable constants. What I try to achieve is to avoid changes to the legacy source code which has to be covered with junit tests. That is the source of such weird requirement. – michael nesterenko Feb 28 '12 at 21:11

2 Answers2

1

You can't. The only possible workaround is to make the constant not final, which makes it, well, not a constant. What you should be doing is reorganizing your code so it can be tested without this sort of madness.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
0

Hm. First caevat: this is a BAD thing to do in production code. I've done it in tests (to switch jdbc driver) but it is not a good idea. But: this is probably doable if you do it early enough. JavaC (assuming you use the Oracle one) does no optimization, it's all done by the the JIT. So, if you change it before the JIT does its magic then it should be fine. In theory,you should be able to change the value after run-time optimization as well, since the JIT:ed code should be marked as no longer valid once you change the string but here I'm skating on very thin ice indeed.

This is (parts) of my test code.

I need to change the driver in this class:

class MysqlConnection {
   private static final String DRIVER_NAME = "com.mysql.jdbc.Driver";

    protected Connection instantiateNewConnection() {
    try {
        // The newInstance() call is a work around for some 
        // broken Java implementations

        Class.forName(DRIVER_NAME).newInstance();
    } catch (Exception ex) {
        // handle the error 
        LOG.info("Class Exception: " + ex);
    }
}
}

And I do it like this:

class DBOperation {
    static {
        Field f = MysqlConnection.class.getDeclaredField("DRIVER_NAME");
        f.setAccessible(true);
        f.set(f, LocalFlipper.HSQLDB_DRIVER);
    }
}

This works. It IS possible to change final fields in java, it's just not a good idea. First I modify the field in question, then I instantiate an instance and the DRIVET_NAME field contains the jdbc driver I want.

There seems to be some doubts about wether this will work or not, but I can assure you it does, try it out for yourselves.

@LouisWasserman: I've gone and javap:ed parts of the code:

Class.forName(DRIVER_NAME).newInstance();

corresponds to

   28:  ldc     #16; //String com.mysql.jdbc.Driver
   30:  invokestatic    #17; //Method  java/lang/Class.forName(Ljava/lang/String;)Ljava/lang/Class;
   33:  invokevirtual   #18; //Method java/lang/Class.newInstance:()Ljava/lang/Object;

As you can see, the String is not inlined. Besides, how do you inline an object (or complex type)? You can inline a reference (naturally). I agree that if we have code like

Class.forName("com.mysql.jdbc.Driver");

Then there is no way to access that string since we can't get a reference to it.

Erik
  • 2,013
  • 11
  • 17
  • The JLS specifies that constant final strings are always inlined. http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#5313 http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#44557 "References to compile-time constants must be resolved at compile time to a copy of the compile-time constant value, so uses of such a field never cause initialization." – Louis Wasserman Feb 28 '12 at 20:30
  • No, it specifies that they are interned, not inlined. See edited post for more – Erik Feb 28 '12 at 21:01
  • The JLS specifies pretty clearly that, for example, compile-time constants from another class does not cause that class to be initialized. There are a few ways for a Java implementation to do that -- for example, copying the constant value to the other class's constant pool -- but using reflection to change it in one class doesn't necessarily propagate. – Louis Wasserman Feb 28 '12 at 21:05
  • Better citation: http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5.3 . "If a final field is initialized to a compile-time constant in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the compile-time constant." That seems to apply perfectly accurately here, since a literal `String` satisfies the conditions to be a compile-time constant. I suspect the JLS may _require_ constants to be inlined, which implies that there is no answer to the OP's question. – Louis Wasserman Feb 28 '12 at 21:07
  • Could you provide an example? I disassembled code and constant was already inlined there. – michael nesterenko Feb 28 '12 at 21:09
  • Huh, problem is not with editing the field, it is with the inlined values, so despite you edited the field all other places will have its compile time value. – michael nesterenko Feb 28 '12 at 21:14