§1.3.1 Acquisition and implicit inheritance of role classes

A team acquires all roles from its super-team. This relation is similar to inheritance of inner classes, but with a few decisive differences as defined next. Two implementation options are mentioned below, which can be used to realize the special semantics of role acquisition (virtual classes and copy inheritance).

Implicit role inheritance
1
public team class S {
2
    protected class R0 {...}
3
    protected class R1 extends R0 {
4
        boolean ok;
5
        R2 m() {...}
6
        void n(R2 r) {...}
7
    }
8
    protected class R2 {...}
9
}
10
public team class T extends S {
11
    @Override protected class R1 {
12
        R2 m() {
13
            if(ok) { return tsuper.m(); }
14
            else { return null; }
15
        }
16
        void doIt() {
17
            n(m());
18
        }
19
    }
20
}

(a) Role class acquisition

A team T which extends a super-team S has one role class T.R corresponding to each role S.R of the super-team. The new type T.R overrides R for the context of T and its roles. Acquisition of role classes can either be direct (see (b) below), or it may involve overriding and implicit inheritance ((c) below).

In the above example (Listing 1.3.1-1) the team S operates on types S.R0, S.R1 and S.R2, while T operates on types T.R0, T.R1 and T.R2.
(Type references like "S.R0" are actually illegal in source code (§1.2.3.(b)). Here they are used for explanatory purposes only)

(b) Direct role acquisition

Within a sub-team T each role S.R of its super-team S is available by the simple name R without further declaration.

The role R2 in Listing 1.3.1-1 can be used in the sub-team T (line 12), because this role type is defined in the super class of the enclosing team.

(c) Overriding and implicit inheritance

If a team contains a role class definition by the same name as a role defined in its super-team, the new role class overrides the corresponding role from the super-team and implicitly inherits all of its features. Such relation is established only by name correspondence.

A role that overrides an inherited role should be marked with an @Override annotation. A compiler should optionally flag a missing @Override annotation with a warning. Conversely, it is an error if a role is marked with an @Override annotation but does not actually override an inherited role.

It is an error to override a role class with an interface or vice versa. A final role cannot be overridden.
Unlike regular inheritance, constructors are also inherited along implicit inheritance, and can be overridden just like normal methods.

In Listing 1.3.1-1 R1 in T implicitly inherits all features of R1 in S. This is, because its enclosing team T extends the team S (line 10) and the role definition uses the same name R1 (line 11). Hence the attribute ok is available in the method m() in T.R1 (line 13). T.R1 also overrides S.R1 which is marked by the @Override annotation in line 11.

(d) Lack of subtyping

Direct acquisition of roles from a super-team and implicit inheritance do not establish a subtype relation. A role of a given team is never conform (i.e., substitutable) to any role of any other team. S.R and T.R are always incommensurable.
Note, that this rule is a direct consequence of §1.2.2.(e).

(e) Dynamic binding of types

Overriding an acquired role by a new role class has the following implication: If an expression or declaration, which is evaluated on behalf of an instance of team T or one of its contained roles, refers to a role R, R will always resolve to T.R even if R was introduced in a super-team of T and even if the specific line of code was inherited from a super-team or one of its roles. Only the dynamic type of the enclosing team-instance is used to determine the correct role class (see below for an example).

A special case of dynamically binding role types relates to so-called class literals (see JLS §15.8.2). Role class literals are covered in §6.1.(c).

The above is strictly needed only for cases involving implicit inheritance. It may, however, help intuition, to also consider the directly acquired role T.R in (b) to override the given role S.R.

In line 17 of Listing 1.3.1-1 the implicitly inherited method n is called with the result of an invocation of m. Although n was defined in S (thus with argument type S.R2, see line 6) in the context of T it expects an argument of T.R2. This is correctly provided by the invocation of m in the context of T.

(f) tsuper

Super calls along implicit inheritance use the new keyword tsuper. While super is still available along regular inheritance, a call tsuper.m() selects the version of m of the corresponding role acquired from the super-team.

See §2.4.2 for tsuper in the context of role constructors.

tsuper can only be used to invoke a corresponding version of the enclosing method or constructor, i.e., an expression tsuper.m() may only occur within the method m with both methods having the same signature (see §2.3.2.(b) for an exception, where both methods have slightly different signatures).

In Listing 1.3.1-1 the role R1 in team T overrides the implicitly inherited method m() from S. tsuper.m() calls the overridden method m() from S.R1 (line 13).

(g) Implicitly inheriting super-types

If a role class has an explicit super class (using extends) this relation is inherited along implicit inheritance.

In Listing 1.3.1-1 the role R1 in T has T.R0 as its implicitly inherited super class, because the corresponding role in the super-team extends R0 (line 3).

Overriding an implicitly inherited super class is governed by §1.3.2.(b), below.
The list of implemented interfaces is merged along implicit inheritance.

(h) Preserving visibility

A role class must provide at least as much access as the implicit super role, or a compile-time error occurs (this is in analogy to JLS §8.4.6.3). Access rights of methods overridden by implicit inheritance follow the same rules as for normal overriding.

(i) Dynamic binding of constructors

When creating a role instance using new not only the type to instantiate is bound dynamically (cf. §1.3.1.(e)), but also the constructor to invoke is dynamically bound in accordance to the concrete type.
Within role constructors all this(..) and super(..) calls are bound statically with respect to explicit inheritance and dynamically with respect to implicit inheritance. This means the target role name is determined statically, but using that name the suitable role type is determined using dynamic binding.
See also §2.5.(a) on using constructors of abstract role classes.

(j) Overriding and compatibility

The rules of JLS §8.4.6 also apply to methods and constructors inherited via implicit inheritance.

(k) Covariant return types

Given a team T1 with two roles R1 and R2 where R2 explicitly inherits from R1, both roles defining a method m returning some type A. Given also a sub-team of T1, T2, where T2.R1 overrides m with a covariant return type B (sub-type of A):

    public team class T1 {
       protected abstract class R1 {
          abstract A m();
       }
       protected class R2 extends R1 {
          A m() { return new A(); }
       }
    }
    public team class T2 extends T1 {
       protected class R1 {
          @Override B m() { return new B(); } // this declaration renders class T2.R2 illegal
       }
    }

In this situation role T2.R2 will be illegal unless also overriding m with a return type that is at least B. Note, that the actual error occurs at the implicitly inherited method T2.R2.m which is not visible in the source code, even T2.R2 need not be mentioned explicitly in the source code. A compiler should flag this as an imcompatibility at the team level, because a team must specialize inherited roles in a consistent way.

Example code (Teams and Roles):
1
public team class MyTeamA {
2
  protected class MyRole {
3
    String name;
4
    public MyRole (String n) { name = n; }
5
    public void print() { System.out.println("id="+name); }
6
  }
7
  protected MyRole getRole() { return new MyRole("Joe"); }
8
}
10
public team class MySubTeam extends MyTeamA {
11
  protected class MyRole {
12
    int age;
13
    public void setAge(int a) { age = a; }
14
    public void print() {
15
      tsuper.print();
16
      System.out.println("age="+age);
17
    }
18
  }
19
  public void doit() {
20
    MyRole r = getRole();
21
    r.setAge(27);
22
    r.print();
23
  }
24
}
25
...
26
MySubTeam myTeam = new MySubTeam();
27
myTeam.doit();
Program output
id=Joe
age=27
Effects:
  • According to §1.3, MyTeamA implements ITeam (line 1).
  • An implicit role inheritance is created for MySubTeam.MyRole (§1.3.1.(c); line 11).
    If we visualize this special inheritance using a fictitious keyword overrides the compiler would see a declaration:
    protected class MyRole overrides MyTeamA.MyRole { ... }
  • Invoking getRole() on myTeam (line 27, 20) creates an instance of MySubTeam.MyRole because the acquired role MyTeamA.MyRole is overridden by MySubTeam.MyRole following the rules of implicit inheritance (cf. §1.3.1.(e)).
  • Overriding of role methods and access to inherited features works as usual.
  • As an example for §1.3.1.(f) see the call tsuper.print() (line 15), which selects the implementation of MyTeamA.MyRole.print.