next up previous
Next: 1.11.3 Exceptions as Errors Up: 1.11 Loose Ends Previous: 1.11.1 Local variables

1.11.2 Casts and Static Type Checking

In Scheme every primitive operation dynamically checks that its arguments have the appropriate form (type) as it executes. If an operation is applied to data of the wrong form, Scheme aborts execution and prints an error message, much as Java does when an exception is thrown and not caught.

Java also performs some argument checking during program execution (run-time), but most argument checking is done statically by the compiler before a Java program executes. A Java compiler enforces a syntactic discipline on program text called static typing. The compiler uses a collection of type-checking rules to determine whether a program is well-typed or not. Programs that are not well-typed are rejected with an explanation of which rules were broken.

The type-checking rules embody simple ``common sense'' inferences and consistency checks. The rules assign a type to every program expression and subsequently check that these type assignments are consistent. A Java compiler assigns types to program expression as follows:

every program constant has a type specified by the language definition;
every program variable (field, parameter, or local variable) has the type declared in the program;
each method invocation has the declared return type of the method;
each application of an arithmetic operator (e.g., +, star) has the return type stipulated by a table in the language definition;1.4
each application of a relational operator and instanceof test has type boolean;
each conditional expression
test ? consequent : alternative
has the more general type of the consequent type and alternative;1.5 and
the type of any cast expression
T e
is T.

Given these type assignments, a Java compiler checks their consistency by enforcing the following rules:

the type of the receiver of a field selection or method invocation includes the specified field or method in its signature (a list of the member headers for the class or interface);
the type assigned to each argument expression in a method invocation is a subtype of the declared type of the corresponding parameter;
the type of the right-hand side of an assignment is a subtype of the type of the left-hand-side; and
the type of each return expression in a method body is a subtype of the declared return type of the method.

Note that Java type checking rules do not capture the logical consequences of instanceof tests. As a result, Java program text often must include apparently redundant casting operations in code following an instanceof test.

This phenomenon is illustrated in the following simple example. Consider the following method which could be added to the IntList class above.

static Object first(IntList l) {
  if (l instanceof Cons) return ((Cons) l).first;
  else throw
    new ClassCastException("first requires a non-Empty IntList"); 
In the method, all occurrences of the parameter l have the same type, namely IntList as declared in the method header. The
l instanceof Cons
test has no effect on type-checking. As a result, the occurrence of l preceding the field extraction operation .first must be explicitly converted to type Cons using the casting operation (Cons) written as a prefix in front of l. Since the field .first is not defined in the abstract class IntList, the definition of the method first does not type check if the casting operation (Cons) is omitted.

Applying a casting operation ( T ) to a Java expression e of some static object type U has consequences for both program execution and compilation. First, it inserts a run-time check to confirm that the value of e belongs to the type T as claimed by the casting operation. Second, it converts the static type of the expression e from U to T.

next up previous
Next: 1.11.3 Exceptions as Errors Up: 1.11 Loose Ends Previous: 1.11.1 Local variables
Corky Cartwright