| << §4.9.2 Role side inheritance | ↑ Table of Contents ↑ |
§4.9.3 Covariant return types
Since version 5, Java supports the covariant redefinition of a method's return type
(see JLS 8.4.5).
This is not supported for callin methods (§4.9.3.(a)).
If base methods with covariant redefinition of the return type are to be bound by a callin binding
the subsequent rules ensure that type safety is preserved.
Two constraints have to be considered:
- When a callin method issues a base-call or calls its tsuper version, this call must produce a value whose type is compatible to the enclosing method's declared return type.
- If a replace-bound role method returns a value that is not the result of a base-call, it must be ensured that the return value actually satisfies the declared signature of the bound base method.
(a) No covariant callin methods
A method declared with the callin modifier that overrides an inherited method
must not redefine the return type with respect to the inherited method.
This reflects that fact that an inherited callin binding should remain type-safe
while binding to the new, overriding role method.
Binding a covariant role method to the original base method would break constraint (1) above.
(b) Capturing covariant base methods
If a callin binding should indeed affect not only the specified base method but also overriding versions which covariantly redefine the return type, the binding must specify the base method's return type with a "+" appended to the type name as in
void rm() <- before RT+ bm();
Without the "+" sign the binding would only capture base methods whose
return type is exactly RT;
by appending "+" also sub-types of RT
are accepted as the declared return type.
(c) Covariant replace binding
When using the syntax of §4.9.3.(b) to capture base methods with
covariant return types in a callin binding with the replace modifier,
the role method must be specified using a free type parameter as follows:
<E extends RT> E rm() <- replace RT+ bm();
The role method rm referenced by this callin binding must use the same style
of return type using a type parameter.
The only possible non-null value of type E
to be returned from such method is the value provided by a base-call or a tsuper-call.
This rule enforces the constraint (2) above.
Note that this rule is further generalized in §4.10.
Binding a parametric role method
| 1 | public class SuperBase { |
| 2 | SuperBase foo() { return this; } |
| 3 | void check() { System.out.print("OK"); } |
| 4 | } |
| 5 | public class SubBase extends SuperBase { |
| 6 | @Override |
| 7 | SubBase foo() { return this; } |
| 8 | void print() { System.out.print("SubBase"); } |
| 9 | String test() { |
| 10 | this.foo().print(); // print() requires a SubBase |
| 11 | } |
| 12 | } |
| 13 | |
| 14 | public team class MyTeam { |
| 15 | protected class R playedBy SuperBase { |
| 16 | callin <E extends SuperBase> E ci() { |
| 17 | E result= base.ci(); |
| 18 | result.check(); // check() is available on E via type bound SuperBase |
| 19 | return result; |
| 20 | } |
| 21 | <E extends SuperBase> E ci() <- replace SuperBase+ foo(); |
| 22 | } |
| 23 | } |
| << §4.9.2 Role side inheritance | ↑ Table of Contents ↑ |
Explanation:
SubBase.fooin line 7 redefines the return type fromSuperBase(inherited version) toSubBase, thus clients like the method call in line 10 must be safe to assume that the return value will always conform toSubBase.fooby specifyingSuperBase+as the expected return type. Thus, if an instance ofMyTeamis active at the method call in line 10, this call tofoowill indeed be intercepted even though this call is statically known to return a value of typeSubBase.E. Since the base call is known to have the exact same signature as its enclosing method, the value provided by the base call is of the same typeEand thus can be safely returned fromci. Note, that no other non-null value is known to have the typeE.SuperBaseas an upper bound for the typeEthe callin methodcimay invoke any method declared in typeSuperBaseon any value of typeE. For an example see the call tocheckin line 18.As an aside note that the above example uses type
SuperBasein an undisciplined way: within roleRthis type is bound usingplayedByand the same type is also used directly (as the upper bound forE). This is considered bad style and it is prohibited ifSuperBaseis imported using an base import (§2.1.2.(d)). Here this rule is neglegted just for the purpose of keeping the example small.