类型检查
前面说到静态类型提供了更多的关于对象的信息,因此编译器可以在以下两种情形下进行更好的类型检查:
• 向一个静态类型的对象发送消息时,编译器可以确保接收者是可以响应该消息的。如果接收者不能访问消息中指定的方法,编译器会报告警告。
• 当把一个静态类型的对象赋值给一个静态类型的变量时,编译器可以确保这种赋值是兼容的。否则会报告警告。
“兼容”的类型之间赋值是不会产生告警的。“兼容”通常是指赋值对象的类型于接收赋值变量的类型是相同的,或者赋值对象的类型是从接收赋值变量的类型派生而来的。如下示例:
Shape *aShape;
Rectangle *aRect;
aRect = [[Rectangle alloc] init];
aShape = aRect;
其中
aRect
被赋值给了
aShape
,这是可以的。因为
rectangle
矩形就是一种
shape
形状——
Rectangle
类是
Shape
类的派生类。但是如果将上面的赋值颠倒过来,编译时就会产生警告。这是因为不是所有的
shape
形状都是
Rectangle
矩形的。图
1-2
就展示了
Shape
和
Rectangle
两个类的关系图。如果赋值运算符两个的表达式的类型中有一方为id类型,编译时就不会进行类型检查。静态类型的对象可以被赋值给任意id类型的变量;id类型的对象业可以被赋值给任意的静态类型变量。这是因为诸如alloc和init方法返回的就是id类型。此时,编译器不能确保把id类型对象赋值给静态类型变量的兼容性的。下面的作法很容易出错,但却是允许的:
Rectangle *aRect;
aRect = [[Shape alloc] init];
返回值和参数的类型
通常情况下,不同类中的同名方法必须拥有相同的返回值和参数类型。这种限制是编译器强制的,目的是为了实现动态绑定。这是因为消息接收者的类型在编译时是不知道的,编译器必须对相同名称的方法作相同的处理。这样以来,就只需要为同一个方法(选择器)创建一份关于其返回值和参数类型的描述。
然而,当想静态类型对象发送消息的时候,消息接收者的类型在编译时就是已知的。编译器能够访问到与之相对应的方法信息。因此就能够突破上面描述的限制。
将派生类静态地转换为基类类型
一个对象是可以被静态地转换为自身类的类型或者是其基类的类型。譬如,所有的对象都是可以静态地转换为NSObject类型的。
然而,编译器只能从变量声明是指定的类型来理解变量的静态类型,同时也是据此类型来进行类型检查的。因此,将一个对象转换为其派生类的类型将会导致编译器认为在运行时会发生的情况与实际运行时发生情况的不一致。
比如,如果将一个Rectangle实例静态地转为Shape类型:
Shape *myRectangle = [[Rectangle alloc] init];
编译时,myRectangle会被当作是Shape类的实例类对待。如果向其发送一个消息来执行Rectangle类的方法
BOOL solid = [myRectangle isFilled];
编译时就会报错。因为isFill的方法是定义在Rectangle类中的,而非Shape类中。
然而,如果向其发送消息来执行一个Shape类中业定义了的方法:
[myRectangle display];
编译时就不会报错。由于Rectangle类重写(override)了该方法,在运行时调用的将是Rectangle类的display方法。
类似地,假设类Upper中定义了worry方法,其返回值为double类型:
-(double) worry;
而其派生类Middle中重写了该方法,并且声明了新的返回值:
-(int) worry;
此时,如果静态地将对象转换为Upper类型,编译器就会认为worry方法返回的是double类型;相反,如果静态地将对象转换为Middle类型,编译器就会认为worry方法返回的是int类型。如果将Middle类型的对象静态地转换为Upper类型,编译器就会通知运行时系统worry方法返回的是double类型,但实际上运行时返回的是int类型,这将导致错误。
静态类型使得同名的方法突破了返回值和参数类型必须相同的限制,但是只有在这些方法是在类继承关系图的不同分支的时候,这样做才是可靠的。