If you want to check if one class inherits another without actually having instances of those classes, you may have read my article on Checking Class Inheritance. However, as the many comments quickly pointed out, the methods of checking this may have some flaws. There were also additional methods posted in the comments that should be added and tested. Today I’m adding them, testing them, and checking all of their validity to find the ultimate approach to check class inheritance.

The main issue of correctness is whether or not the method returns true for all of these cases: (note: ClassInheritanceChecker is the main class of the app and derives directly from Sprite)

  • isSubclassOf(ClassInheritanceChecker, ClassInheritanceChecker)
  • isSubclassOf(ClassInheritanceChecker, Sprite)
  • isSubclassOf(ClassInheritanceChecker, DisplayObject)
  • isSubclassOf(InterfaceTop, InterfaceTop)
  • isSubclassOf(InterfaceMiddle, InterfaceTop)
  • isSubclassOf(InterfaceBottom, InterfaceTop)
  • isSubclassOf(ClassTop, InterfaceTop)
  • isSubclassOf(ClassMiddle, InterfaceTop)
  • isSubclassOf(ClassBottom, InterfaceTop)

The method should return false in all of these cases:

  • isSubclassOf(Sprite, ClassTop)
  • isSubclassOf(Sprite, ClassInheritanceChecker)
  • isSubclassOf(DisplayObject, ClassInheritanceChecker)
  • isSubclassOf(InterfaceTop, InterfaceMiddle)
  • isSubclassOf(InterfaceTop, InterfaceBottom)
  • isSubclassOf(InterfaceTop, ClassMiddle)
  • isSubclassOf(InterfaceTop, ClassBottom)

To test correctness, I used the two version from before by Skyboy and player_03 and added versions by Bloodhound, Lab9, and myself. Here is the test app that performs all of the above tests on all of these versions:

package
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getQualifiedSuperclassName;
 
	public class ClassInheritanceChecker extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function ClassInheritanceChecker()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			var logger:TextField = __logger;
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			//log(describeType(ClassInheritanceChecker));return;
			//log(describeType(InterfaceBottom));return;
 
			const classNameRegExp:RegExp = /\[class ([a-zA-Z0-9]+)\]/;
			function getClassName(c:Class): String { return classNameRegExp.exec(String(c))[1]; }
 
			for each (var name:String in ["P03","Skyboy","Bloodhound","Lab9","Jackson"])
			{
				var funcName:String = "isSubclassOf" + name;
				var func:Function = this[funcName];
				var allPass:Boolean = true;
				log(name + ":");
				function test(a:Class, b:Class, expected:Boolean): void
				{
					var aName:String = getClassName(a);
					var bName:String = getClassName(b);
					var result:Boolean;
					try
					{
						result = func(a, b);
					}
					catch(err:Error)
					{
					}
					if (result != expected)
					{
						log("\t\t" + funcName + "(" + aName + ", " + bName + ") = " + result);
						allPass = false;
					}
				}
 
				test(ClassInheritanceChecker, ClassInheritanceChecker, true);
				test(ClassInheritanceChecker, Sprite, true);
				test(ClassInheritanceChecker, DisplayObject, true);
				test(InterfaceTop, InterfaceTop, true);
				test(InterfaceMiddle, InterfaceTop, true);
				test(InterfaceBottom, InterfaceTop, true);
				test(ClassTop, InterfaceTop, true);
				test(ClassMiddle, InterfaceTop, true);
				test(ClassBottom, InterfaceTop, true);
 
				test(Sprite, ClassTop, false);
				test(Sprite, ClassInheritanceChecker, false);
				test(DisplayObject, ClassInheritanceChecker, false);
				test(InterfaceTop, InterfaceMiddle, false);
				test(InterfaceTop, InterfaceBottom, false);
				test(InterfaceTop, ClassMiddle, false);
				test(InterfaceTop, ClassBottom, false);
 
				log("\t\t" + (allPass?"PASS":"FAIL"));
			}
		}
 
		private function isSubclassOfSkyboy(a:Class, b:Class): Boolean
		{
			if (int(!a) | int(!b)) return false;
			return (a == b || a.prototype instanceof b);
		}
 
		private function isSubclassOfP03(type:Class, superClass:Class): Boolean
		{
			if (superClass == Object)
			{
				return true;
			}
			try
			{
				for (
					var c:Class = type;
					c != Object;
					c = Class(getDefinitionByName(getQualifiedSuperclassName(c)))
				)
				{
					if (c == superClass)
					{
						return true;
					}
				}
			}
			catch(e:Error)
			{
			}
 
			return false;
		}
 
		private function isSubclassOfBloodhound(a:Class, b:Class): Boolean
		{
			return b.prototype.isPrototypeOf( a.prototype );
		}
 
		private function isSubclassOfLab9(tested:Class, type:Class): Boolean
		{
			var desc:XML = XML(describeType(tested));            
			var name:String = getQualifiedClassName(type);
 
			return Boolean(desc.factory.extendsClass.@type.contains(name)
				|| desc.factory.implementsInterface.@type.contains(name));
		}
 
		private function isSubclassOfJackson(a:Class, b:Class): Boolean
		{
			if (a == b)
			{
				return true;
			}
 
			var factoryNode:XMLList = XML(describeType(a)).factory;
			var superclassName:String = getQualifiedClassName(b);
			return factoryNode.extendsClass.@type.contains(superclassName)
				|| factoryNode.implementsInterface.@type.contains(superclassName);
		}
	}
}
interface InterfaceTop {}
interface InterfaceMiddle extends InterfaceTop {}
interface InterfaceBottom extends InterfaceMiddle {}
class ClassTop implements InterfaceTop {}
class ClassMiddle implements InterfaceMiddle {}
class ClassBottom implements InterfaceBottom {}

When run in Flash Player 11.2, here is the output:

P03:
		isSubclassOfP03(InterfaceMiddle, InterfaceTop) = false
		isSubclassOfP03(InterfaceBottom, InterfaceTop) = false
		isSubclassOfP03(ClassTop, InterfaceTop) = false
		isSubclassOfP03(ClassMiddle, InterfaceTop) = false
		isSubclassOfP03(ClassBottom, InterfaceTop) = false
		FAIL
Skyboy:
		isSubclassOfSkyboy(InterfaceMiddle, InterfaceTop) = false
		isSubclassOfSkyboy(InterfaceBottom, InterfaceTop) = false
		isSubclassOfSkyboy(ClassTop, InterfaceTop) = false
		isSubclassOfSkyboy(ClassMiddle, InterfaceTop) = false
		isSubclassOfSkyboy(ClassBottom, InterfaceTop) = false
		FAIL
Bloodhound:
		isSubclassOfBloodhound(ClassInheritanceChecker, ClassInheritanceChecker) = false
		isSubclassOfBloodhound(InterfaceTop, InterfaceTop) = false
		isSubclassOfBloodhound(InterfaceMiddle, InterfaceTop) = false
		isSubclassOfBloodhound(InterfaceBottom, InterfaceTop) = false
		isSubclassOfBloodhound(ClassTop, InterfaceTop) = false
		isSubclassOfBloodhound(ClassMiddle, InterfaceTop) = false
		isSubclassOfBloodhound(ClassBottom, InterfaceTop) = false
		FAIL
Lab9:
		isSubclassOfLab9(ClassInheritanceChecker, ClassInheritanceChecker) = false
		isSubclassOfLab9(InterfaceTop, InterfaceTop) = false
		FAIL
Jackson:
		PASS

There is quite a range of correctness here, provided you define “correct” as passing all of these tests:

  • Bloodhound: 7 fails
  • Skyboy: 5 fails
  • player_03: 5 fails
  • Lab9: 2 fails
  • Jackson: 0 fails
  • My version was created based on Lab9’s version with the goal of passing all of the tests. Since the only tests not passed by Lab9’s version were when the two classes or interfaces were the same, I simply checked for this case. The only other changes were to cache the xml.factory lookup and to remove the unnecessary Boolean conversion of the result.

    With a correctness pecking order established, how about speed? To test, I’ve created a small app that runs all of the above tests many times per method:

    package
    {
    	import flash.display.DisplayObject;
    	import flash.display.Sprite;
    	import flash.display.StageAlign;
    	import flash.display.StageScaleMode;
    	import flash.text.TextField;
    	import flash.text.TextFieldAutoSize;
    	import flash.utils.describeType;
    	import flash.utils.getDefinitionByName;
    	import flash.utils.getQualifiedClassName;
    	import flash.utils.getQualifiedSuperclassName;
    	import flash.utils.getTimer;
     
    	public class ClassInheritanceSpeed extends Sprite
    	{
    		private var __logger:TextField = new TextField();
    		private function row(...cols): void
    		{
    			__logger.appendText(cols.join(",")+"\n");
    		}
     
    		public function ClassInheritanceSpeed()
    		{
    			var logger:TextField = __logger;
    			logger.autoSize = TextFieldAutoSize.LEFT;
    			addChild(logger);
     
    			row("--All Versions--");
    			row("Version", "Time");
     
    			var beforeTime:int;
    			var afterTime:int;
    			var REPS:int = 500;
    			var i:int;
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfSkyboy(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfSkyboy(ClassInheritanceSpeed, Sprite);
    				isSubclassOfSkyboy(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfSkyboy(InterfaceTop, InterfaceTop);
    				isSubclassOfSkyboy(InterfaceMiddle, InterfaceTop);
    				isSubclassOfSkyboy(InterfaceBottom, InterfaceTop);
    				isSubclassOfSkyboy(ClassTop, InterfaceTop);
    				isSubclassOfSkyboy(ClassMiddle, InterfaceTop);
    				isSubclassOfSkyboy(ClassBottom, InterfaceTop);
     
    				isSubclassOfSkyboy(Sprite, ClassTop);
    				isSubclassOfSkyboy(Sprite, ClassInheritanceSpeed);
    				isSubclassOfSkyboy(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfSkyboy(InterfaceTop, InterfaceMiddle);
    				isSubclassOfSkyboy(InterfaceTop, InterfaceBottom);
    				isSubclassOfSkyboy(InterfaceTop, ClassMiddle);
    				isSubclassOfSkyboy(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("Skyboy", (afterTime-beforeTime));
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfP03(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfP03(ClassInheritanceSpeed, Sprite);
    				isSubclassOfP03(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfP03(InterfaceTop, InterfaceTop);
    				isSubclassOfP03(InterfaceMiddle, InterfaceTop);
    				isSubclassOfP03(InterfaceBottom, InterfaceTop);
    				isSubclassOfP03(ClassTop, InterfaceTop);
    				isSubclassOfP03(ClassMiddle, InterfaceTop);
    				isSubclassOfP03(ClassBottom, InterfaceTop);
     
    				isSubclassOfP03(Sprite, ClassTop);
    				isSubclassOfP03(Sprite, ClassInheritanceSpeed);
    				isSubclassOfP03(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfP03(InterfaceTop, InterfaceMiddle);
    				isSubclassOfP03(InterfaceTop, InterfaceBottom);
    				isSubclassOfP03(InterfaceTop, ClassMiddle);
    				isSubclassOfP03(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("P03", (afterTime-beforeTime));
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfBloodhound(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfBloodhound(ClassInheritanceSpeed, Sprite);
    				isSubclassOfBloodhound(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfBloodhound(InterfaceTop, InterfaceTop);
    				isSubclassOfBloodhound(InterfaceMiddle, InterfaceTop);
    				isSubclassOfBloodhound(InterfaceBottom, InterfaceTop);
    				isSubclassOfBloodhound(ClassTop, InterfaceTop);
    				isSubclassOfBloodhound(ClassMiddle, InterfaceTop);
    				isSubclassOfBloodhound(ClassBottom, InterfaceTop);
     
    				isSubclassOfBloodhound(Sprite, ClassTop);
    				isSubclassOfBloodhound(Sprite, ClassInheritanceSpeed);
    				isSubclassOfBloodhound(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfBloodhound(InterfaceTop, InterfaceMiddle);
    				isSubclassOfBloodhound(InterfaceTop, InterfaceBottom);
    				isSubclassOfBloodhound(InterfaceTop, ClassMiddle);
    				isSubclassOfBloodhound(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("Bloodhound", (afterTime-beforeTime));
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfLab9(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfLab9(ClassInheritanceSpeed, Sprite);
    				isSubclassOfLab9(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfLab9(InterfaceTop, InterfaceTop);
    				isSubclassOfLab9(InterfaceMiddle, InterfaceTop);
    				isSubclassOfLab9(InterfaceBottom, InterfaceTop);
    				isSubclassOfLab9(ClassTop, InterfaceTop);
    				isSubclassOfLab9(ClassMiddle, InterfaceTop);
    				isSubclassOfLab9(ClassBottom, InterfaceTop);
     
    				isSubclassOfLab9(Sprite, ClassTop);
    				isSubclassOfLab9(Sprite, ClassInheritanceSpeed);
    				isSubclassOfLab9(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfLab9(InterfaceTop, InterfaceMiddle);
    				isSubclassOfLab9(InterfaceTop, InterfaceBottom);
    				isSubclassOfLab9(InterfaceTop, ClassMiddle);
    				isSubclassOfLab9(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("Lab9", (afterTime-beforeTime));
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfJackson(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfJackson(ClassInheritanceSpeed, Sprite);
    				isSubclassOfJackson(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfJackson(InterfaceTop, InterfaceTop);
    				isSubclassOfJackson(InterfaceMiddle, InterfaceTop);
    				isSubclassOfJackson(InterfaceBottom, InterfaceTop);
    				isSubclassOfJackson(ClassTop, InterfaceTop);
    				isSubclassOfJackson(ClassMiddle, InterfaceTop);
    				isSubclassOfJackson(ClassBottom, InterfaceTop);
     
    				isSubclassOfJackson(Sprite, ClassTop);
    				isSubclassOfJackson(Sprite, ClassInheritanceSpeed);
    				isSubclassOfJackson(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfJackson(InterfaceTop, InterfaceMiddle);
    				isSubclassOfJackson(InterfaceTop, InterfaceBottom);
    				isSubclassOfJackson(InterfaceTop, ClassMiddle);
    				isSubclassOfJackson(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("Jackson", (afterTime-beforeTime));
     
    			row("");
    			row("--Fast Versions Only--");
    			row("Version", "Time");
     
    			REPS = 1000000;
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfSkyboy(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfSkyboy(ClassInheritanceSpeed, Sprite);
    				isSubclassOfSkyboy(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfSkyboy(InterfaceTop, InterfaceTop);
    				isSubclassOfSkyboy(InterfaceMiddle, InterfaceTop);
    				isSubclassOfSkyboy(InterfaceBottom, InterfaceTop);
    				isSubclassOfSkyboy(ClassTop, InterfaceTop);
    				isSubclassOfSkyboy(ClassMiddle, InterfaceTop);
    				isSubclassOfSkyboy(ClassBottom, InterfaceTop);
     
    				isSubclassOfSkyboy(Sprite, ClassTop);
    				isSubclassOfSkyboy(Sprite, ClassInheritanceSpeed);
    				isSubclassOfSkyboy(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfSkyboy(InterfaceTop, InterfaceMiddle);
    				isSubclassOfSkyboy(InterfaceTop, InterfaceBottom);
    				isSubclassOfSkyboy(InterfaceTop, ClassMiddle);
    				isSubclassOfSkyboy(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("Skyboy", (afterTime-beforeTime));
     
    			beforeTime = getTimer();
    			for (i = 0; i < REPS; ++i)
    			{
    				isSubclassOfBloodhound(ClassInheritanceSpeed, ClassInheritanceSpeed);
    				isSubclassOfBloodhound(ClassInheritanceSpeed, Sprite);
    				isSubclassOfBloodhound(ClassInheritanceSpeed, DisplayObject);
    				isSubclassOfBloodhound(InterfaceTop, InterfaceTop);
    				isSubclassOfBloodhound(InterfaceMiddle, InterfaceTop);
    				isSubclassOfBloodhound(InterfaceBottom, InterfaceTop);
    				isSubclassOfBloodhound(ClassTop, InterfaceTop);
    				isSubclassOfBloodhound(ClassMiddle, InterfaceTop);
    				isSubclassOfBloodhound(ClassBottom, InterfaceTop);
     
    				isSubclassOfBloodhound(Sprite, ClassTop);
    				isSubclassOfBloodhound(Sprite, ClassInheritanceSpeed);
    				isSubclassOfBloodhound(DisplayObject, ClassInheritanceSpeed);
    				isSubclassOfBloodhound(InterfaceTop, InterfaceMiddle);
    				isSubclassOfBloodhound(InterfaceTop, InterfaceBottom);
    				isSubclassOfBloodhound(InterfaceTop, ClassMiddle);
    				isSubclassOfBloodhound(InterfaceTop, ClassBottom);
    			}
    			afterTime = getTimer();
    			row("Bloodhound", (afterTime-beforeTime));
    		}
     
    		private function isSubclassOfSkyboy(a:Class, b:Class): Boolean
    		{
    			if (int(!a) | int(!b)) return false;
    			return (a == b || a.prototype instanceof b);
    		}
     
    		private function isSubclassOfP03(type:Class, superClass:Class): Boolean
    		{
    			if (superClass == Object)
    			{
    				return true;
    			}
    			try
    			{
    				for (
    					var c:Class = type;
    					c != Object;
    					c = Class(getDefinitionByName(getQualifiedSuperclassName(c)))
    				)
    				{
    					if (c == superClass)
    					{
    						return true;
    					}
    				}
    			}
    			catch(e:Error)
    			{
    			}
     
    			return false;
    		}
     
    		private function isSubclassOfBloodhound(a:Class, b:Class): Boolean
    		{
    			return b.prototype.isPrototypeOf( a.prototype );
    		}
     
    		private function isSubclassOfLab9(tested:Class, type:Class): Boolean
    		{
    			var desc:XML = XML(describeType(tested));            
    			var name:String = getQualifiedClassName(type);
     
    			return Boolean(desc.factory.extendsClass.@type.contains(name)
    				|| desc.factory.implementsInterface.@type.contains(name));
    		}
     
    		private function isSubclassOfJackson(a:Class, b:Class): Boolean
    		{
    			if (a == b)
    			{
    				return true;
    			}
     
    			var factoryNode:XMLList = XML(describeType(a)).factory;
    			var superclassName:String = getQualifiedClassName(b);
    			return factoryNode.extendsClass.@type.contains(superclassName)
    				|| factoryNode.implementsInterface.@type.contains(superclassName);
    		}
    	}
    }
    interface InterfaceTop {}
    interface InterfaceMiddle extends InterfaceTop {}
    interface InterfaceBottom extends InterfaceMiddle {}
    class ClassTop implements InterfaceTop {}
    class ClassMiddle implements InterfaceMiddle {}
    class ClassBottom implements InterfaceBottom {}

    I ran this performance test in the following environment:

    • Flex SDK (MXMLC) 4.6.0.23201, compiling in release mode (no debugging or verbose stack traces)
    • Release version of Flash Player 11.2.202.229
    • 2.4 Ghz Intel Core i5
    • Mac OS X 10.7.3

    And here are the results I got:

    All Versions:
    Version Time
    Skyboy 1
    P03 31
    Bloodhound 1
    Lab9 3628
    Jackson 2925

    Class Inheritance Performance (all versions)

    Since two of the tests (Skyboy and Bloodhound) were far and away faster than the rest, I tested them against each other with more iterations and got these results:

    Fast Versions Only:
    Version Time
    Skyboy 832
    Bloodhound 1170

    Class Inheritance Performance (fast versions)

    Correctness, if you define it like I do above, clearly comes at a price. However, you may not care if the function returns true when the same class is passed in for both parameters. Does a class or interface really subclass itself? The answer is mostly semantics. Are you interested in interfaces? If not, there are some pleasing alternatives. If you can live with a simpler implementation like Skyboy’s or Lab9’s, a 3000x speedup awaits you!

    Spot a bug? Have a suggestion? Post a comment!