Saturday, November 24, 2007

More on ActionScript 3 String Equality Comparison Operators (== Versus ===)

In my last blog entry, I contrasted String equality comparison in ActionScript 3 with String equality comparison in Java. In that blog entry, I brought up the difference in the == and === comparison operators. Generally, it doesn't matter which of these are used in ActionScript when strict type checking is on (compiled with -static=true option) because the compiler reports errors when checking values of two different data types against each other. However, when the application is compiled with -static=false, two values of different data types can be compared and that is where the difference between == and === become interesting (they actually are different!).

For this example, I'll start with the code in the last blog entry, but it is enhanced with some more comparisons. In particular, this example code compares an int against a String with == and ===. As written in the example below, the code only compiles with static explicitly set to false. To make the the code compile with -static=true, the highlighted lines must be changed as described after the example.


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:flash.display="flash.display.*"
width="750" height="500"
applicationComplete="compareComparisonOperators();">

<mx:Script>
<![CDATA[
/**
* To compile solely this Flex application from the command line, use
* mxmlc -strict=true -output StrictStringComp.swf StringComparison2.mxml
* and
* mxmlc -strict=false -output NotStrictStringComp.swf StringComparison2.mxml
*
* This application demonstrates use of and nuances associated with
* ActionScript 3 String comparisons.
*/

import mx.containers.Panel;
import mx.controls.Alert;

private const firstString:String = "Privacy";

/**
* Demonstrate how == and === work with Strings in ActionScript.
*/
private function compareComparisonOperators():void
{
const origString:String = "An Interesting String";
const sortOfSameString:String = "An Interesting String";
const sameString:String = origString;

const panel:Panel = new Panel();

const compareOrigStringToSortSameStringWith2Eq:Boolean =
origString == sortOfSameString;
const compareOrigStringToSortSameStringWith3Eq:Boolean =
origString === sortOfSameString;

const origStrToSortSameStr2Eq:Label = new Label();
origStrToSortSameStr2Eq.text =
"Original string == same chars: "
+ String(compareOrigStringToSortSameStringWith2Eq);
panel.addChild(origStrToSortSameStr2Eq);

const origStrToSortSameStr3Eq:Label = new Label();
origStrToSortSameStr3Eq.text =
"Original string === same chars: "
+ String(compareOrigStringToSortSameStringWith3Eq);
panel.addChild(origStrToSortSameStr3Eq);

// Result in same Strings, but of different types.
const origNumberString:String = "111";
const origNumber:int = 111;

const compareNumberStringToNumberWith2Eq:Boolean =
origNumberString == origNumber;
const compareNumberStringToNumberWith3Eq:Boolean =
origNumberString === origNumber;

const numStrToNum2Eq:Label = new Label();
numStrToNum2Eq.text =
"Numeric content String == Numeric: "
+ String(compareNumberStringToNumberWith2Eq);
panel.addChild(numStrToNum2Eq);

const numStrToNum3Eq:Label = new Label();
numStrToNum3Eq.text =
"Numeric content String === Numeric: "
+ String(compareNumberStringToNumberWith3Eq);
panel.addChild(numStrToNum3Eq);

const intValue:int = 10;
const uintValue:uint = 10;
const compareIntToUInt2Eq:Boolean = intValue == uintValue;
const compareIntToUInt3Eq:Boolean = intValue === uintValue;

const intToUInt2Eq:Label = new Label();
intToUInt2Eq.text = "int == uint: "
+ String(compareIntToUInt2Eq);
panel.addChild(intToUInt2Eq);

const intToUInt3Eq:Label = new Label();
intToUInt3Eq.text = "int === uint: "
+ String(compareIntToUInt3Eq);
panel.addChild(intToUInt3Eq);

compareComparisonOperatorsBox.addChild(panel);
}

/**
* Compare string entered into input text (providedString) to constant
* String assigned to firstString.
*/
private function compareString():void
{
const textInputString:String = providedString.text;
if ( textInputString == firstString )
{
statusValue.text = "Same Strings!";
statusValue.setStyle("color", "0x00FF00");
statusResultValue.setStyle("color", "0x00FF00");
}
else
{
statusValue.text = textInputString
+ " is not the same String.";
statusValue.setStyle("color", "0xFF0000");
statusResultValue.setStyle("color", "0xFF0000");
}
providedString.text="";
statusResultValue.text = String(textInputString == firstString);
}
]]>
</mx:Script>

<mx:HDividedBox id="mainBox" width="100%" height="100%">
<mx:VBox id="interactiveComparisons" width="50%" height="100%">
<mx:Label id="stringPrompt"
text="Enter a String to compare to '{firstString}':" />
<mx:TextInput id="providedString" enter="compareString();"/>
<mx:HBox>
<mx:Label id="statusLabel" text="Status" />
<mx:Label id="statusValue" text="None tried" />
</mx:HBox>
<mx:HBox>
<mx:Label id="statusResultLabel" text="Status Result" />
<mx:Label id="statusResultValue" text="None tried" />
</mx:HBox>
</mx:VBox>
<mx:VBox id="compareComparisonOperatorsBox" width="50%">
</mx:VBox>
</mx:HDividedBox>

</mx:Application>


As the comments in the code itself suggest, this can be compiled with the static flag turned on and with it turned off. I have added the -output option to differentiate the compiled .swf files produced for each setting of that flag. The -output and -strict flag are described in more detail in About the Application Compiler Options - Flex 2.01.

When running the version generated with -strict=false (NotStrictStringComp.swf), the following output is generated (click on image to make it larger):



There are a few interesting observations from the output in the snapshot above:

1. The == and === comparison operators provide different results when comparing the String "111" to the int 111. The equality operator == returns true when comparing the String "111" to the int 111 while the strict equality operator === returns false for the same comparison. The reason for the different result is that == checks only for equality in terms of value (so "111" does resolve to the same value as 111) while the === operator checks for value equality (like ==) and adds on a check for same data type as well (so false because int and String are not the same data type).

2. Unlike Java's == comparison used on Strings, neither ActionScript equality comparison operator actually checks to make sure the two compared values are the identical piece of data. In Java, when == is used to compare two Strings, it is check that they are not only the same value of data, but are exactly the same piece of data. ActionScript's == is more like Java's String.equals() and its === operator is unlike anything Java has (or needs) because Java is a statically typed language.

3. The comparison operators == and === return true when comparing an int to an unsigned int. This demonstrates that the strict equality comparison operator treats int and uint as the same data type because they are in the same type family. In fact, comparing an int and a unit even compiles when the -static=true flag is sent.


If one tries to compile the code above as-is in strict mode, compiler errors are reported when attempting to compile the code that compares a String to an int. The snapshot below (click on it to make it larger) shows these error messages.



The obvious observation here is that while comparing a String to an int produces different results for == (true if the String value is the same as the int value) and === (returns false no matter what the values of the String and int because they are different data types), comparing a String and int will not even compile in static mode.

To make the code above compile, one must surround the int in the comparison with the global function String(). This converts the int to a String for the comparison and the compiler allows this through. However, in such a case, there will not be any difference between behavior of == and === because now two Strings (same data type) are being compared for equality.

The edited relevant code to make it compile in strict mode is shown next.


const compareNumberStringToNumberWith2Eq:Boolean =
origNumberString == String(origNumber);
const compareNumberStringToNumberWith3Eq:Boolean =
origNumberString === String(origNumber);


Here is the output of running the slightly edited code compiled in static mode (click on image to make it larger):



As expected, all comparisons with == and === return true for same values because they also happen to be of same type in all cases.

No comments: