Better Ways To Check Class Inheritance
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
- 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
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:
And here are the results I got:
All Versions:
Version | Time |
---|---|
Skyboy | 1 |
P03 | 31 |
Bloodhound | 1 |
Lab9 | 3628 |
Jackson | 2925 |
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 |
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!
#1 by Martin Heidegger on April 30th, 2012 ·
A bit of caching and its suddenly acceptably fast: http://wonderfl.net/c/tNkF
#2 by skyboy on May 3rd, 2012 ·
I toyed around with various ideas on how to make the describeType version faster and found none. Though, I did make it more robust and universally applicable: Any combination of Class or Object can be passed in. For a minor speed penalty.