3.3 Compatible Types

 <  Day Day Up  >  

The following discussion assumes an understanding of inheritance in OOP. If you're not familiar with that concept, you may want to skip this section for now and return to it once you've read Chapter 6.

Earlier we learned that a variable of one type can store only a value of a compatible type . Intuitively, we understand that the String type is incompatible with the Number type (that is, putting aside for a moment that it is possible to convert between strings and numbers , we know that a string is not a number). But the phrase "compatible type" has a very precise technical meaning. A type, X , is compatible with another type, Y , if X is of type Y , or if X is any subtype of Y .

For example, suppose we have a class, Ball , and a subclass, Basketball . A variable of type Ball can store a Basketball instance because the Basketball type is a subtype (i.e., subclass) of Ball :

 var ball1:Ball = new Basketball( );  // Legal! 

The preceding code works because the compiler knows that every Basketball instance has (through inheritance) all the properties and methods of the Ball class. The converse , however, is not true; every Ball instance does not necessarily have the properties and methods of the Basketball class. Therefore, the following code yields a compile-time type mismatch error:

 var ball2:Basketball = new Ball( );  // Illegal! 

On first glance, you might think that the preceding examples seem backward, but they are not. In the first example, we store a Basketball instance in the variable ball1 , whose declared datatype is Ball . The compiler allows this even though the Basketball subclass might define methods and properties not supported by the Ball superclass. However, the compiler displays an error if we try to access a method or property on ball1 that isn't supported by the Ball class (i.e., the datatype of ball1 ) even if such method or property is defined for the Basketball class. For example, suppose that the Basketball subclass defines an inflate( ) method but the Ball superclass does not. The second line of the following code example causes the compiler to display an error:

 var ball1:Ball = new Basketball( );  // Legal, so far... ball1.inflate( );                    // But this causes a compiler error! 

Thus, the compiler gives us a little rope and we have to avoid the temptation to hang ourselves . As programmers, we need to be smart enough not to access methods and parameters of the Basketball class on ball1 , unless they are also supported by the Ball class.

The compiler checks the datatype of the variable ( ball1 ) ”not the class of the object actually stored in the variable ”to determine what methods and properties are available.


That explanation might be clear, but it is admittedly counterintuitive. You may be wondering whether there is some way to make use of the Basketball -ness of the Basketball instance stored in ball1 , even though the variable's datatype is Ball . We'll answer that question under "Casting," later in this chapter. And we'll revisit our Basketball and Ball classes under "Polymorphism and Dynamic Binding" in Chapter 6.

Now let's return to the converse example in which we attempted to store a Ball instance in the variable ball2 , whose datatype is Basketball :

 var ball2:Basketball = new Ball( );  // Illegal! 

What would happen if the compiler allowed the preceding assignment? At some point later in the program, we might try to access methods and properties of the Basketball subclass on the ball2 instance, but those methods and properties would not be supported (the object we stored in ball2 is a Ball , not a Basketball !). Therefore, the compiler prevents us from storing a Ball instance in a Basketball -typed variable to prevent potential runtime errors resulting from accessing methods or properties of the Basketball class on an instance of the Ball class.

In the following example, if the first line were allowed by the compiler, we'd get into trouble later. The second line seems reasonable at compile time (it invokes the inflate( ) method on what it thinks is a Basketball instance). But at runtime, the interpreter would attempt to invoke inflate( ) on what is in fact a Ball instance (stored in ball2 ). The invocation would fail because the Ball class doesn't define the inflate( ) method:

 var ball2:Basketball = new Ball( ); // If this were allowed at compile time... ball2.inflate( );                   // this would cause a runtime error! 

Make sense? If not, remember that all basketballs are balls, but not all balls are basketballs. We can treat any basketball as a ball (even if we don't take advantage of all its features), but we can't treat any ball like a basketball. For example, inflating a bowling ball wouldn't work very well!

The rule of thumb is to declare the variable to be of a more general type than the content placed in it (i.e., you can place data of the subtype's class within a variable declared to be of the supertype 's class).


The ability to use subtypes wherever a given type is expected enables an important OOP feature: polymorphism . We'll return to subtypes and polymorphism in Chapter 6.

3.3.1 Handling Any Datatype

In ActionScript 2.0, we can make a variable, property, parameter, or return value accept data of any type by specifying Object as the datatype. For example, here we declare the datatype of container as Object . We can subsequently store an instance of any class in it without error:

 var container:Object = new Date( ); // No error. container = new Color( );           // No error. 

This technique works because the Object class is the superclass of all ActionScript classes, so all types are compatible with the Object type (primitive values such as the Boolean true or the string "hello world" are not, strictly speaking, instances of any class but are still considered compatible with the Object type).

But be careful not to overuse this technique, as it effectively disables the compiler's ability to generate type errors for the variable, property, parameter, or return value in question. The compiler won't complain when you refer to a nonexistent property or method on an item whose datatype is Object . For example, the following code generates no errors:

 var container:Object = new Date( ); // No error. trace(container.toString( ));       // Execute   toString( )   normally. container.blahblahblah( );          // Invoke nonexistent method. No error. trace(container.foobarbaz);        // Access nonexistent property. No error. 

The toString( ) method executes normally because it is supported by all objects, but no error occurs for the invocation of blahblahblah( ) and the access of foobarbaz , despite the fact that they are not defined by any class.

Using the Object datatype is not the only way to elude ActionScript 2.0's type checking; we'll discuss several others shortly, under "Circumventing Type Checking."

3.3.2 Compatibility with null and undefined

In an object-oriented ActionScript 2.0 program, it's typical to store null or undefined in a variable as an indication of an absence of data or an uninitialized variable. Knowing this, you might wonder how a variable can store null if its datatype is, say, MovieClip . Rest easy ”unlike other values, the null and undefined types can be used anywhere , regardless of the type of container they are stored in:

 var target:MovieClip = null;  // Legal. function square (x:Number):Number {   return x*x; } square(null);  // Legal. function square (x:Number):Number {   if (x == 0) {     return null;  // Legal.   }   return x*x; } 

Declaring something's type to be Object makes it a universal recipient, like a person with type AB positive blood who can accept any type of blood you give her. On the other hand, null and undefined are universal donors, like people with type O negative blood whose blood can be transfused into any other person without harm.

This flexibility allows us to use the null type to indicate an empty value for any data container. For example, here we construct a new TextFormat object, using null for arguments we wish to leave empty:

 // Specify emphasis format, but not other font information. var tf:TextFormat = new TextFormat(null, null, null, true); 

Compatibility with undefined also allows ActionScript to assign undefined to parameters, variables , and properties that have never been assigned a value. For example, here we define a method that displays a message on screen, complete with the message sender's name. If the name of the message sender is not provided, ActionScript sets the sentBy parameter to undefined , in which case the method uses the name "Anonymous":

 public function displayMsg (msg:String, sentBy:String):Void {   // Use "Anonymous" if a name was not supplied.   if (sentBy == undefined) {     sentBy = "Anonymous";   }   // Display the message in a text field.   output.text = sentBy + ": " + msg; } 

 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net