Enums and ActionScript's Static Initializers

I discovered today, while trying to synthesize an Enum type, that AS3 has the concept of a static initializer, which is awesome. In a nutshell, a static initializer is kind of like a constructor, but it's for the class object itself, not instances of the class. It gets invoked during classloading, after all static properties have been set, but the class is turned loose for general consumption. Here's an example of an Enum type (named ColorEnum) that uses the static initializer (in bold):

package com.barneyb.test {

  public class ColorEnum {

    public static const BLACK:ColorEnum = new ColorEnum(0x000000);
    public static const WHITE:ColorEnum = new ColorEnum(0xFFFFFF);

    private static var locked:Boolean = false;

    {
      locked = true;
    }

    private var _color:uint;
    public function get color():uint {
      return _color;
    }

    public function ColorEnum(color:uint) {
      if (locked) {
        throw new Error("You can't instantiate ColorEnum");
      }
      _color = color;
    }
  }
}

What this provides is a ColorEnum class that cannot be instantiated, with two instances stored in the BLACK and WHITE static constants. This sort of class locking is equally useful for singletons (think ServiceLocator if you use Cairngorm, though they implement it differently). Usually, you use a private constructor for this type of behaviour, but since AS3 doesn't support that, you have to synthesize it. However, that's not all a static initializer can do.

Another use case is if you have a complex static variable that you need to initialize, but can't do in a single expression. For example, if you need to create multiple aggregated objects, you usually need multiple expressions. You can do this in a static initializer as well. Note that you can't set a static const within a static initializer, but you can set a static var (variables).

20 responses to “Enums and ActionScript's Static Initializers”

  1. Jared Rypka-Hauer

    Barney,

    It's pseudo-constructors for everyone, then, eh? :) Granted, post-classloading vs post-instantiation, but given that CF and AS3's paradigms are actually radically different, the analogy seems to resonate with me.

    J

  2. Static initializers in AS3

    [...] barneyb No [...]

  3. » typesafe enums in as3

    [...] when trying to create a typesafe enum class, back in November of last year. (I noticed a similar post on BarneyBlog from a couple days earlier, [...]

  4. » typesafe enum abstract class

    [...] a small elaboration of Barney B's typesafe enumeration example, I have built an abstract class on which to base AS3 [...]

  5. Jason Boyd

    Having seen several approaches to the Singleton case in AS3 (non-public class in constructor, using a random and/or private number, etc), this one works pretty well with minimal fuss:

    package foo
    {
    class Singleton
    {
    private static const m_instance:Singleton = new Singleton();

    public function Singleton()
    {
    if (m_instance != null) throw new Error("Noooooooo!!!!");
    // one time init code here
    }

    public static function getInstance():Singleton
    {
    return m_instance;
    }
    }
    }

    Static initializers are cool though. Thanks for the post!

  6. Jason Boyd

    True and true. I tend to code all my AS3 assuming single threads, since AS3 is single-threaded. If they change this in AS4 or whenever, all bets are off.

  7. ActionScript Enums « The Combined Corner
  8. ActionScript 3 Enums « The Combined Corner

    [...] different ways of faking Enums in ActionScript 3, I decided to do my own based on Scott's and Barney's [...]

  9. Avangel

    Many thanks for this code trick :) I was trying to find solutions to emulate enums in AS3 for a while, without success. Your solutions is pretty good. It's a shame that AS does not enable protected/private contructors, it would have been even easier…

  10. Enumerations with Class « Flex Insights
  11. Jordan

    I think that I have found a better way to do it, you don't need the locked variable
    and with the "private" class in the constructor it can't be instantiated ,
    unless you pass null to the constructor , then runtime Error will occur :)!

    package {
    	final public class ColorEnum  {
    		//STATIC
    		public static const BLACK:ColorEnum = new ColorEnum(0x000000,null);
    		public static const WHITE:ColorEnum = new ColorEnum(0xFFFFFF,null);
    
    		//INSTANCE
    		private var _color:uint
    
    		public function get color():uint  {
    			return _color
    		}
    
    		function ColorEnum(color:uint,block_:block_constructor)  {
    			if (ColorEnum) throw new Error("You can't instantiate ColorEnum");
    			_color = color;
    		}
    	}
    }
    
    class block_constructor {}
    
  12. Jason Boyd

    Amendment to the singleton pattern above. A runtime error on attempting instantiation might suprise users who's tools or docs show a plain old default constructor. Likewise requiring a "dummy" parameter of a private class is clunky. Not that this isn't…

    public class Foo {
    private static const MAGIC_NUM:Number = Math.random();
    private static const INSTANCE:Foo = new Foo(MAGIC_NUM);

    public function Foo(_magicNum) {
    if (_magicNum != MAGIC_NUM)
    throw new Error("Singleton, dummy!");
    }

    public function getInstance():Foo
    {
    return INSTANCE;
    }
    }

  13. BetaDesigns( Blog ).toString( ); » Enums and static initializers in AS3

    [...] post 1 [...]

  14. jloa

    omg…. Ok, guys, here's what is called a real singleton class:

    package
    {
      public class Singleton
      {
        private static var INSTANCE:Singleton;
    
        public static function getInstance():Singleton
        {
           if(!INSTANCE) INSTANCE = new Singleton(new SingletonKey());
           return INSTANCE;
        }
    
        public function Singleton(key:SingletonKey = null)
        {
          if(!key) throw new Error("Singleton class");
        }
      }
    }
    internal class SingletonKey {}
    
  15. Leandro Zanol

    Hi Barney,

    I've struggled with static initializers for a while, the way you've done it'll not allow you to run other code (such as "for", "if", etc.) than allocations.
    The way I found to solve this was through anonymous function:

    (function() {

    })();

    LZ

  16. Will Del Genio

    I second Leandro's approach of using anonymous functions to assign values to static initializers.

  17. Glen Flint

    Thanks for your informative post! Here's an enumeration class that draws upon you insights…

    package common
    {
    	import flash.utils.Dictionary;
    
    	public class Enumeration
    	{
    		private var type:String;
    		private var name:String;
    
    		private static var initialized:Dictionary = new Dictionary();
    
    		private static var inOrder:Dictionary = new Dictionary();
    
    		private static var byName:Dictionary = new Dictionary();
    
    		public function Enumeration(type:String, name:String)
    		{
    			if (initialized[type] != null) {
    				throw new Error(type + " is an enumeration.  New is not allowed.");
    			}
    
    			this.type = type;
    			this.name = name;
    
    			storeInOrder();
    
    			storeByName();
    		}
    
    		private function storeByName():void {
    			if (byName[ this.type ] == null) {
    				byName[ this.type ] = new Dictionary();
    			}
    
    			byName[ this.type ][ this.name ] = this;
    		}
    
    		private function storeInOrder():void {
    			if (inOrder[ this.type ] == null) {
    				inOrder[ this.type ] = new Array();
    			}
    
    			inOrder[ this.type ].push(this);
    		}
    
    		public function toString():String {
    			return this.name;
    		}
    
    		protected static function getAll(type:String):Array {
    			if (! initialized[type]) {
    				throw new(type + " not initialized yet!");
    			}
    
    			return inOrder[ type ].concat();   // copy so no one can mess it up
    		}
    
    		protected static function fromString(type:String, name:String):Enumeration {
    			if (! initialized[type]) {
    				throw new(type + " not initialized yet!");
    			}
    
    			if (byName[ type ][ name ] == null) {
    				throw new Error(type + " name unrecognized: " + name);
    			}
    
    			return byName[ type ][ name ];
    		}
    
    		protected static function initialize(type:String):void {
    			initialized[type] = true;
    		}
    	}
    }
    

    Here's some code to test it…

    package suite.cases.common
    {
    
    	import flash.errors.IllegalOperationError;
    
    	import org.flexunit.Assert;
    	import org.flexunit.assertThat;
    
    	public class EnumerationTest
    	{
    		[BeforeClass]
    		public static function runBeforeClass():void {
    			// run for one time before all test cases
    		}   
    
    		[AfterClass]
    		public static function runAfterClass():void {
    			// run for one time after all test cases
    		}   
    
    		[Before(order=1)]
    		public function runBeforeEveryTest():void {
    			// run before every test
    		}
    
    		[After]
    		public function runAfterEveryTest():void {
    			// run after every test
    		} 		
    
    		[Test]
    		public function testToString():void {
    			Assert.assertEquals("Apple", Fruit.APPLE.toString());
    			Assert.assertEquals("Banana", Fruit.BANANA.toString());
    			Assert.assertEquals("Orange", Fruit.ORANGE.toString());
    		}
    
    		[Test]
    		public function testFromString():void {
    			Assert.assertEquals(Fruit.APPLE, Fruit.fromString("Apple"));
    			Assert.assertEquals(Fruit.BANANA, Fruit.fromString("Banana"));
    			Assert.assertEquals(Fruit.ORANGE, Fruit.fromString("Orange"));
    		}
    
    		[Test]
    		public function testGetAll():void {
    			Assert.assertEquals(
    				[ Fruit.APPLE, Fruit.BANANA, Fruit.ORANGE ].toString(),
    				Fruit.getAll().toString());
    		}
    
    		[Test(expects="Error")]
    		public function anotherFruit():void {
    			new Fruit("PLUM");
    		}
    	}
    }
    
    import common.Enumeration;
    
    class Fruit extends Enumeration {
    
    	private static const FRUIT:String = "Fruit";
    
    	public static const APPLE:Fruit = new Fruit("Apple");
    	public static const BANANA:Fruit = new Fruit("Banana");
    	public static const ORANGE:Fruit = new Fruit("Orange");
    
    	Enumeration.initialize(FRUIT);		
    
    	public function Fruit(name:String) {
    		super(FRUIT, name);
    	}
    
    	public static function getAll():Array {
    		return Enumeration.getAll(FRUIT);
    	}
    
    	public static function fromString(name:String):Fruit {
    		return Enumeration.fromString(FRUIT, name) as Fruit;
    	}
    }