Class Bootup Part 2
Today’s article follows up on an article I wrote way back in August of 2009 about the order of operations when you use a class. In the original article I showed the order of field initializers and constructors. Today I’m expanding on that to show three more chunks of code that are run. Can you guess what those chunks are?
The first new chunk of code that’s executed when you first use a class is the chunk that initializes static variables (and constants, but those are just syntax sugar). This chunk is executed the first time you use a class and allows you a convenient (and mandatory, in the case of constants) way of initializing static fields. This chunk looks like this:
class MyClass { public static const MY_MAGIC_NUMBER:int = 12345; }
The second new chunk is the “class constructor” or “static initialization block” or, for you avid bytecode readers, “cinit()”. This block—which is different from the “instance constructor” that’s called when you call new
—is executed the first time you use a class and is intended to help you do setup work for the class, not an instance of the class. Here’s an example:
class MyClass { public static var doubles:Vector.<int>; // class constructor { // NOTE: like other static functions, you can't use "this" here // Initialize the vector with double the index const numDoubles:int = 100; doubles = new Vector.<int>(numDoubles); for (var i:int = 0; i < numDoubles; ++i) { doubles[i] = i*2; } } }
The third and final chunk of code is yet-another one-time block, this time presumably to set up the package the class is in. I call this the “package constructor”, for lack of a better term. If you throw an exception (which you can’t do explicitly due to an MXMLC crash but can do implicitly by calling a function like new BitmapData(0,0)
), you’ll see a stack trace that calls it “global$init()”. So, I suppose, it’s sort of a global space like exists in C but not in Java. Here’s an example of how to use it:
package mypackage { // "package constructor"/"global init()" MySingleton.init(); MyOtherSingleton.init(); public class MyClass { } }
Honestly, I’m not entirely sure what useful code you could put there that wouldn’t better belong elsewhere (your main class’ constructor, class constructors, etc.). The only useful purpose I’ve seen it serve was pointed out in a comment on last week’s article showing how to use it for class dependencies:
package mypackage { // Class dependencies. Note the class is referenced after the import. import otherpackage.ClassA; ClassA; import otherpackage.ClassB; ClassB; // This class intentionally left blank public class ClassDependencies {} }
Now, with those additional chunks of code in mind, let’s look at two classes I’ve written to test the order of execution:
ClassBootupParent
package { import flash.display.*; ClassBootupParent.history.push("parent package constructor"); public class ClassBootupParent extends Sprite { public static var history:Array = ["parent static field initializer"]; public var parentField:* = history.push("parent non-static field initializer"); { history.push("parent class constructor"); } public function ClassBootupParent() { history.push("parent constructor"); } } }
ClassBootup
package { import flash.utils.*; import flash.text.*; import flash.display.*; ClassBootupParent.history.push("child package constructor"); public class ClassBootup extends ClassBootupParent { public static var childStatic:* = history.push("child static field initializer"); public var childNonStaticField:* = history.push("child non-static field initializer"); { history.push("child class constructor"); } public function ClassBootup() { history.push("child constructor"); var tf:TextField = new TextField(); tf.autoSize = TextFieldAutoSize.LEFT; tf.text = history.join("\n"); addChild(tf); } } }
The history
fiel is used to keep a journal describing the order. When run, the app display this:
parent static field initializer parent class constructor parent package constructor child static field initializer child class constructor child package constructor child non-static field initializer parent non-static field initializer parent constructor child constructor
First of all, it’s good to see that the chunks of code execute in at least a semi-reasonable order. First the parent statics are executed, then the child statics. Both sets of statics are executed in the same order: field initializer, class constructor, package constructor. Then, when the class is actually instantiated, we see the same bizarre results from last time. Why is it that the order is child-parent-parent-child? Add in the fact that even calling your parent’s constructor is optional and the instantiation process gets even uglier.
In conclusion, we’ve seen that the static initialization happens—as expected—before the object instantiation we saw last time. Here is the full order for your reference:
- Parent Statics
- Field initializer (
public static var x:int = 3;
) - Class constructor (
class A { {/*class constructor*/} }
) - Package constructor (
package P { /* package constructor */ class A {} }
)
- Field initializer (
- Child Statics
- Field initializer (
public static var x:int = 3;
) - Class constructor (
class A { {/*class constructor*/} }
) - Package constructor (
package P { /* package constructor */ class A {} }
)
- Field initializer (
- Child non-static field initializer (
public var x:int = 3;
) - Parent non-static field initializer (
public var x:int = 3;
) - Parent constructor (optional) (
function Parent(){}
) - Child constructor (
function Child(){}
)
#1 by as3isolib on January 31st, 2011 ·
given the strange startup behavior in the instance instantiation, do you think this has any carry over into non-constructor methods? For instance, overridden getters/setters that you find ALL OVER the place in the MX framework’s invalidation mechanism? Certainly not… I hope!
While functions are already slower than var access, it would be extremely disappointing to see thisconvoluted process taking place in inheritance/function paradigm we all take for granted
#2 by jackson on January 31st, 2011 ·
I think normal methods, including getters and setters, work pretty much as expected. Only the child’s version gets called and then you can optionally call the parent’s version via the
super
keyword. The bootup stuff is weird primarily because there are multiple “chunks” of code and no clear order in which they’re executed. For example, I wouldn’t exactly call the child-parent-parent-child order of object initialization obvious.#3 by divillysausages on February 1st, 2011 ·
Actually, it does kind of make sense. I’m assuming that you’re running ClassBootup as the main class for the project/SWF.
The first piece of code it comes across is “ClassBootupParent.history.push(“child package constructor”);”. For this to work, the compiler starts the creation of the ClassBootupParent class. So in order:
1) parent static field initializer
I’d be curious to see if this is called first because it’s a static property and it’s initialised immediately after declaration (as in, if you had another public static Array declared and initialised after, would it also appear). My guess is that it appears first because of the first line of code: “ClassBootupParent.history.push()” -> it needs to create the history Array before this can happen (order of execution working from right to left here)
2) parent class constructor
The static class constructor. Called when the class is first referenced (in the first line of code). Note that in this block, the class itself doesn’t exist yet – if you try to call “ClassBootupParent.history” instead of just “history” here, you’ll throw a runtime null exception.
3) parent package constructor
This comes next as it’s the first piece of code in the ClassBootupParent file, but we need to create the ClassBootupParent class and the history Array before it can be used.
4) child static field initializer
5) child class constructor
These two follow the same logic as above. The class constructor is called when the class is first referenced, and as it pushes something into the ClassBootup array, it needs to be created.
6) child package constructor
Our original first piece of code. Now it has everything at the basic state needed to fulfil this (ClassBootupParent class exists to the extent that we can add stuff to the history Array, while ClassBootup is our main class so needs to be instantiated in order for the code to run)
7) child non-static field initializer
8) parent non-static field initializer
The ClassBootup is still being created, so we create our properties. My guess is that the Flash compiler creates the child properties before creating the parent properties – perhaps because of the way it checks for conflicts?
9) parent constructor
Calling super() is only optional if there’s no parameters to be passed in the constructor. If you don’t explicitly call it, it’s implicitly called at the start of the ClassBootup constructor. If you specifically call super() after the history.push() call in ClassBootup, these last two trace() messages will swap around.
10) child constructor
Finally finishes the ClassBootup class creation with the call of its constructor.
#4 by jackson on February 1st, 2011 ·
That’s a pretty good way of explaining the “why” of the class bootup. I think that the use of
history
has less to do with the order and more to do with the test. It’s true that the parent’s bootup is kicked off by usage of the parent, but I think that could have been done by a simple explicit reference to the class. In fact, that reference is unavoidable as you must explicitly state the class you derive from. Therefore, whenClassBootup
is used—by the way, it is the main class of the SWF—it triggers the bootup for its parent and then itself. This part, and even order, makes good sense to me.I still don’t like the order of the non-static bootup. It should either be child-child-parent-parent or parent-parent-child-child, not some bizarre mix. It’s true that calling the parent constructor is only optional when there is a constructor with no required arguments, but that in itself makes the bootup even more complex! A constructor with no required arguments is not always a default constructor and therefore not always a no-op; there could be very important side-effects like setting up the class so it doesn’t crash on subsequent method calls.
Thanks for the walkthrough. I’m off to go check out your site! :)
#5 by Roland Zwaga on February 3rd, 2011 ·
Hey guys,
I´ve been fooling around with AS3 bytecode quite a bit while developing as3commons-bytecode (http://www.as3commons.org/as3-commons-bytecode/index.html) and I can confirm that this:
{
history.push(“child class constructor”);
}
public function ClassBootup()
{
history.push(“child constructor”);
var tf:TextField = new TextField();
tf.autoSize = TextFieldAutoSize.LEFT;
tf.text = history.join(“\n”);
addChild(tf);
}
will eventually be re-written by the compiler to this:
public function ClassBootup()
{
history.push(“child class constructor”);
history.push(“child constructor”);
var tf:TextField = new TextField();
tf.autoSize = TextFieldAutoSize.LEFT;
tf.text = history.join(“\n”);
addChild(tf);
}
also, this:
public var parentField:* = history.push(“parent non-static field initializer”);
{
history.push(“parent class constructor”);
}
public function ClassBootupParent()
{
history.push(“parent constructor”);
}
will end up like this:
public var parentField:*;
public function ClassBootupParent()
{
parentField = history.push(“parent non-static field initializer”);
history.push(“parent class constructor”);
history.push(“parent constructor”);
}
the order in which the statements are added to the constructor is the same as the appear in the source code.
I used the swfdump.exe utility (you can find this in the bin folder of your SDK) to find out how classes were
constructed at the bytecode level.
Cheers,
Roland
#6 by Roland Zwaga on February 3rd, 2011 ·
Oh, and as for Class and Instance instantiation. At bytecode level there is indeed a ClassInfo block and a InstanceInfo block. The instance info can contain methods, slots, constructors, etc, etc. The ClassInfo only contains a constructor and slot and/or constant traits, the constructor is filled with all the static initializations in the order in which they appear in the sourcecode.
Custom namespaces, for example, are also added as traits to the ClassInfo.
cheers,
Roland
#7 by jackson on February 3rd, 2011 ·
Thanks for the info!