A Subtyping Question

Consider the following classes.

    public abstract class TypeT {

      public abstract TypeR method(TypeP param)
              throws TypeE;

    }

    public class TypeTPrime
    extends TypeT {

      public TypeRPrime method(TypePPrime param)
              throws TypeEPrime {
        return new TypeRPrime();
      }

    }
    

Given that

what kind of type relationships must exist between

in order for typing to work correctly? In other words, what conditions must the compiler enforce in order to ensure that messages and assignments in the following example work even when the object referenced by obj is a member of class TypeTPrime?

      ...
      TypeT obj = ...;
      ...
      try {
        TypeR result = obj.method(actualParam);
      } catch(TypeE ex) {
        ...
      }
    

Answer

The crucial issue concerns assignment compatibility. There is one explicit assignment to the variable result. In addition, there are two implicit assignments: one to the parameter of method and one to the exception parameter ex. A compiler only knows the type of obj, not its class. The language must define rules that ensure that when a compiler approves the typing in the try statement, all of these assignments will be type compatible at run time.

Suppose the language imposes the following rules when it accepts TypeTPrime as a subtype of TypeT:

Then all of the assignments will be compatible at run time. If these conditions are not met then a compiler must flag class TypeTPrime as failing to meet its requirements as a subtype of TypeT.

Covariance and Contravariance

The rules described above are the most liberal that a language can allow. A language could have more stringent rules. For example, it could require that TypeTPrime's method declaration match types in TypeT's method declaration exactly.

If a language allows more freedom with regard to return values and exceptions then it supports covariance - it allows subclasses to implement methods with return type and exception types that are subtypes of the parent class's declared types.

If a language allows more freedom with regard to parameters then it supports contrariance - it allows subclasses to implement methods with parameter types that are supertypes of the parent class's declared types.

The Java programming supports covariance, allowing subclasses to implement parent class methods with subtype declarations for return values and exceptions. However, Java does not support contravariance; subclasses that change the parent class's declaration of method parameters are flagged as failing to implement the requirements of the parent class.