6 Replies Latest reply: Aug 18, 2005 12:22 PM by Shigeru Chiba RSS

Setting a field in a constructor before super is called

Howard Lovatt Newbie

I want to set a field in a constructor before super is called so that the super constructor can use the field, e.g.:

abstract class Base {
 Base() { System.out.println( getField() ); }
 abstract int getField();
}

class Derived extends Base {
 int field;
 Derived() {
 field = 1;
 super();
 }
 int getField() { return field; }
}


The above isn't valid Java (the call to super must be the first line of constructor Derived) and it is also not valid in Javassist. Is there a way round this in Javassist?

Note: this setting of a field before calling super is similar to how instance inner (nested) classes work, they set the field that points to their outer class before calling their own super constructor.


  • 1. Re: Setting a field in a constructor before super is called
    Shigeru Chiba Expert

     

    Note: this setting of a field before calling super is similar to how instance inner (nested) classes work, they set the field that points to their outer class before calling their own super constructor.


    I don't think so... Setting the field that points to the outer class
    should be done *after* calling the super constructor.


  • 2. Re: Setting a field in a constructor before super is called
    Howard Lovatt Newbie

    I am pretty certain I am right, inner classes initialize their pointer to the outer field before calling their own super. EG:

    abstract class Base {
     Base() {
     System.out.println( getField() );
     }
    
     abstract int getField();
    }
    

    The constructor calls the abstract method getField and if this abstract method is implemented with an instance inner class then all is OK, e.g.:
    public class InnerTest {
     int field = 1;
    
     Base base() {
     return new Base() {
     int getField() {
     return field;
     }
     };
     }
    
     public static void main( final String[] notUsed ) {
     new InnerTest().base();
     }
    }
    

    This prints 1 as you would expect. But if you hand code the inner class then it won't work, e.g.:
    class Derived extends Base {
     final ExternalTest outer;
    
     Derived( final ExternalTest outer ) {
     this.outer = outer;
     }
    
     int getField() {
     return outer.field;
     }
    }
    

    public class ExternalTest {
     int field = 1;
    
     Base base() {
     return new Derived( this );
     }
    
     public static void main( final String[] notUsed ) {
     new ExternalTest().base();
     }
    }
    

    This gives a NullPointerException since the field outer is not initialized when constructor Derived calls its super constructor, Base. Also: you can see if you dissassemble the code for InternalTest$1 that it initializes its pointer to the outer class, this$0, before calling its super.

    What the inner class does is equivalent to:
     Derived( final ExternalTest outer ) {
     this.outer = outer;
     super();
     }
    }
    

    Which is illegal in Java because super is always called first.

    My question is how can I do this in Javassist which also requires the call to super to be the first line.


  • 3. Re: Setting a field in a constructor before super is called
    Howard Lovatt Newbie

    You can by the way use Javassist to do what I want, but it is a bit of a pain! You can write a program that modifies Derived above so that it calls super after initializing the field:

    package examples.vmts.initializationorder.chibaexample;
    
    
    import javassist.*;
    import javassist.bytecode.*;
    
    
    public class ModifyDerived {
     public static void main( final String[] notUsed ) throws NotFoundException,
     BadBytecode,
     CannotCompileException,
     java.io.IOException {
     final CtClass ctClass = ClassPool.getDefault()
     .getAndRename( "examples.vmts.initializationorder.chibaexample.Derived",
     "examples.vmts.initializationorder.chibaexample.ModifiedDerived" );
     ctClass.replaceClassName(
     "examples.vmts.initializationorder.chibaexample.ExternalTest",
     "examples.vmts.initializationorder.chibaexample.ModifiedTest" );
    
     final ClassFile clazz = ctClass.getClassFile();
     final MethodInfo constructor = clazz.getMethod( MethodInfo.nameInit );
     final CodeAttribute code = constructor.getCodeAttribute();
     final byte[] bytes = code.getCode();
     final CodeIterator codeIter = code.iterator();
     codeIter.skipSuperConstructor(); // move to super call, this is what skipSuperConstructor does!
    
     if ( codeIter.hasNext() ) { // need to modify clazz, i.e. super not last opcode
    
     final int superSize = codeIter.next(); // index of opcode after super call
     final int rest = bytes.length - superSize - 1; // number of bytes up to return opcode
     final byte[] superCall = new byte[ superSize ];
     System.arraycopy( bytes, 0, superCall, 0, superSize );
     System.arraycopy( bytes, superSize, bytes, 0, rest );
     System.arraycopy( superCall, 0, bytes, rest, superSize );
     }
    
     ctClass.writeFile( "C:\\Personal\\Java\\" );
     }
    }
    

    The above takes class Derived and modifies it, the new class is similar except that:

    1. Its name is ModifiedDerived
    2. Its field and constructor arguments are of type ModifiedTest
    3. And this is the important one, it calls super after it has initialized the field

    You can test the modified class with:
    package examples.vmts.initializationorder.chibaexample;
    
    
    public class ModifiedTest {
     int field = 1;
    
     Base base() {
     return new ModifiedDerived( this );
     }
    
     public static void main( final String[] notUsed ) {
     new ModifiedTest().base();
     }
    }
    

    Which gives the expected result of 1.

    As I said at the start all this is a bit of a pain, any chance that the Javassist compiler will relax the restriction that super must be first?

    Keep up the good work,

    Howard.

  • 4. Re: Setting a field in a constructor before super is called
    Shigeru Chiba Expert

    Thanks. According to my simple study, javac of jdk1.4 and
    javac of 1.5 seem to compile an inner class in a different way.
    I found that javac 1.5 initializes a point to the outer class
    before calling the super constructor. On the other hand,
    javac 1.4 does not do so. Thus, your sample code of InnerTest
    throws a NullPointerException if it is compiled by javac 1.4.

    It might be only a pointer to the outer class that can be initialized
    before calling a super constructor. I'll study this more.

  • 5. Re: Setting a field in a constructor before super is called
    Howard Lovatt Newbie

    My Sun 1.4.2 javac initializes the pointer first. Also see the end of the first sub-section in the how it works section of the 1997 specification:

    http://www.flex-compiler.csail.mit.edu/jdk/guide/innerclasses/spec/innerclasses.doc2.html

    This implies that it is erronous not to initialize this$0 before calling super.

  • 6. Re: Setting a field in a constructor before super is called
    Shigeru Chiba Expert

    I found a simpler way to generate Derived with Javassist.

    abstract class Base {
     Base() { System.out.println( getField() ); }
     abstract int getField();
    }
    
    class Derived extends Base {
     int field;
     Derived() {
     field = 1;
     super();
     }
     int getField() { return field; }
    }
    


    To generate the Derived class above, first generate the following class:

    class Derived extends Base {
     int field;
     Derived() {
     super();
     }
     int getField() { return field; }
    }
    


    Then, call insertBefore() on the CtConstructor representing the constructor of Derived.
    The inserted source code is something like this:

    field = 1;


    Note that insertBefore() inserts the code before super().
    insertBeforeBody() inserts the code after super(), though.

    I know this post is too late for Howard but it might be useful for others. :-)

    Chiba