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) { ... }
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:
method
must return a subtype of
TypeR,
method
must either not throw an exception or
throw an exception which is a subtype of TypeE, and
method
must accept a parameter which is a
supertype of TypeP.
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.
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.