9. Classes

Class declarations introduce new reference types and describe the manner of their implementation.

Classes can be top-level and local (see Local Classes and Interfaces).

A class body contains declarations and class initializers.

Declarations can introduce class members (see Class Members) or class constructors (see Constructor Declaration).

The body of the declaration of a member comprises the scope of a declaration (see Scopes).

Class members include:

  • Fields,

  • Methods, and

  • Accessors.

Class members can be declared or inherited.

Every member is associated with the class declaration it is declared in.

Field, method, accessor and constructor declarations can have the following access modifiers (see Access Modifiers):

  • Public,

  • Protected,

  • Internal, or

  • Private.

A newly declared field can hide a field declared in a superclass or superinterface.

A newly declared method can hide, implement, or override a method declared in a superclass or superinterface.

Every class defines two scopes (see Scopes): one for instance members, and the other for static members. This means that two members of a class can have the same name if one is static while the other is not.

The body of the declaration of a method (see Method Declarations) comprises the scope of a declaration (see Scopes).


9.1. Class Declarations

Every class declaration defines a Class type, i.e., a new named reference type.

The class name is specified by the identifier inside a class declaration.

If typeParameters are defined in a class declaration, then that class is a generic class (see Generic Declarations).

classDeclaration:
    classModifier? 'class' identifier typeParameters?
      classExtendsClause? implementsClause? classBody
    ;

classModifier:
    'abstract' | 'final'
    ;

The scope of a class declaration is specified in Scopes.

An example of a class is presented below:

 1 class Point {
 2   public x: number
 3   public y: number
 4   public constructor(x : number, y : number) {
 5     this.x = x
 6     this.y = y
 7   }
 8   public distanceBetween(other: Point): number {
 9     return Math.sqrt(
10       (this.x - other.x) * (this.x - other.x) +
11       (this.y - other.y) * (this.y - other.y)
12     )
13   }
14   static origin = new Point(0, 0)
15 }

9.1.1. Abstract Classes

A class with the modifier abstract is known as abstract class. Abstract classes can be used to represent notions that are common to some set of more concrete notions.

A compile-time error occurs if an attempt is made to create an instance of an abstract class:

1abstract class X {
2   field: number
3   constructor (p: number) { this.field = p }
4}
5let x = new X (666)
6  // Compile-time error: Cannot create an instance of an abstract class.

Subclasses of an abstract class can be non-abstract or in turn abstract. A non-abstract subclass of an abstract superclass can be instantiated. As a result, a constructor for the abstract class, and field initializers for non-static fields of that class are executed.

1abstract class Base {
2   field: number
3   constructor (p: number) { this.field = p }
4}
5
6class Derived extends Base {
7   constructor (p: number) { super(p) }
8}

A method with the modifier abstract is considered an abstract method (see Abstract Methods). Abstract methods do not have bodies, i.e., they can be declared but not implemented.

Only abstract classes can have abstract methods. A compile-time error occurs if a non-abstract class has an abstract method:

1class Y {
2  abstract method (p: string): void
3  /* Compile-time error: Abstract methods can only
4     be within an abstract class. */
5}

A compile-time error occurs if an abstract method declaration contains the modifiers final or override.


9.1.2. Final Classes

Final classes are described in the chapter Experimental Features (see Final Classes).


9.1.3. Class Extension Clause

All classes except class Object can contain the extends clause that specifies the base class, or the direct superclass of the current class. A class that has no extends clause, and is not Object, is assumed to have the extends Object clause.

The class that inherits from its superclass is called subclass of that superclass.

classExtendsClause:
    'extends' typeReference
    ;

A compile-time error occurs if:

  • An extends clause appears in the definition of the class Object, which is the top of the type hierarchy, and has no superclass.

  • The class type named by typeReference is not accessible (see Scopes).

  • The ‘extends’ graph has a cycle.

  • typeReference refers directly, or as alias of types primitive, enum, union, interface, or function.

  • Any type argument of typeReference is a wildcard type argument.

Class extension implies that a class inherits all members of the direct superclass. Notice that private members are also inherited from the superclass but they are not accessible within the subclass.

 1 class Base {
 2   // All methods are mutually accessible in the class where
 3       they were declared
 4   public publicMethod () {
 5     this.protectedMethod()
 6     this.privateMethod()
 7   }
 8   protected protectedMethod () {
 9     this.publicMethod()
10     this.privateMethod()
11   }
12   private privateMethod () {
13     this.publicMethod();
14     this.protectedMethod()
15   }
16 }
17 class Derived extends Base {
18   foo () {
19     this.publicMethod()    // OK
20     this.protectedMethod() // OK
21     this.privateMethod()   // compile-time error:
22                            // the private method is inaccessible
23   }
24 }

The transitive closure of a direct subclass relationship is the subclass relationship. Class A can be a subclass of class C if:

  • A is the direct subclass of C; or

  • A is a subclass of some class B, which is in turn a subclass of C (i.e., the definition applies recursively).

Class C is a superclass of class A if A is its subclass.


9.1.4. Class Implementation Clause

A class can implement one or more interfaces. Interfaces that are to be implemented by a class are listed in the implements clause. Interfaces listed in this clause are direct superinterfaces of the class.

implementsClause:
    'implements' interfaceTypeList
    ;

interfaceTypeList:
    typeReference (',' typeReference)*
    ;

A compile-time error occurs if:

  • typeReference fails to name an accessible interface type (see Scopes).

  • Any type argument of typeReference is a wildcard type argument.

  • An interface is repeated as a direct superinterface in a single implements clause (even if that interface is named differently).

For the class declaration C <F1,…, Fn> (\(n\geq{}0\), \(C\neq{}Object\)):

  • Direct superinterfaces of the class type C <F1,…, Fn> are the types specified in the implements clause of the declaration of C (if there is an implements clause).

For the generic class declaration C <F1,…, Fn> (n > 0):

  • Direct superinterfaces of the parameterized class type C < T1,…, Tn> are all types I <U1\(\theta{}\),…, Uk\(\theta{}\)> if:

    • Ti (\(1\leq{}i\leq{}n\)) is a type;

    • I <U1,…, Uk> is the direct superinterface of C <F1,…, Fn>; and

    • \(\theta{}\) is the substitution [F1:= T1,…, Fn:= Tn].

Interface type I is a superinterface of class type C if I is one of the following:

  • Direct superinterface of C;

  • Superinterface of J which is in turn a direct superinterface of C (see Superinterfaces and Subinterfaces that defines superinterface of an interface); or

  • Superinterface of the direct superclass of C.

A class implements all its superinterfaces.

A compile-time error occurs if a class is at the same time a subtype of:

  • Two interface types that represent different instantiations of the same generic interface (see Generic Declarations); or

  • The instantiation of a generic interface, and a raw type that names the a generic interface.

If a class is not declared abstract, then:

  • Any abstract method of each direct superinterface is implemented (see Overriding by Instance Methods) by a declaration in that class.

  • The declaration of the existing method is inherited from a direct superclass, or a direct superinterface.

If a default method (see Default Method Declarations) of a superinterface is not inherited, then that default method can:

  • Be overridden by a class method; and

  • Behave as specified in its default body.

A single method declaration in a class is allowed to implement methods of one or more superinterfaces.

A compile-time error occurs if a class field and a method from one of superinterfaces that a class implements have the same name, except when one is static and the other is not.


9.1.5. Implementing Interface Properties

A class must implement all properties from all interfaces (see Implementing Interface Properties) which are defined as a getter, a setter, or both. Providing implementation for the property in the form of a field is not necessary.

 1 interface Style {
 2   get color(): string
 3   set color(s: string)
 4 }
 5
 6 class StyleClassOne implements Style {
 7   color: string = ""
 8 }
 9
10 class StyleClassTwo implements Style {
11   private color_: string = ""
12
13   get color(): string {
14     return this.color_
15   }
16
17   set color(s: string) {
18     this.color_ = s
19   }
20 }

9.2. Class Body

A class body can contain declarations of the following members:

  • Fields,

  • Methods,

  • Accessors,

  • Constructors, and

  • Static initializers for the class.

classBody:
    '{'
       classBodyDeclaration* classInitializer? classBodyDeclaration*
    '}'
    ;

classBodyDeclaration:
    accessModifier?
    ( constructorDeclaration
    | classFieldDeclaration
    | classMethodDeclaration
    | classAccessorDeclaration
    )
    ;

Declarations can be inherited or immediately declared in a class. Any declaration within a class has a class scope. The class scope is fully defined in Scopes.


9.3. Class Members

Class members are as follows:

Class members declared private are not accessible to all subclasses of the class.

Class members declared protected or public are inherited by subclasses that are declared in a package other than the package containing the class declaration.

Constructors and class initializers are not members, and are not inherited.

Members can be as follows:

A method is defined by the following:

  1. Type parameter, i.e., the declaration of any type parameter of the method member.

  2. Argument type, i.e., the list of types of arguments applicable to the method member.

  3. Return type, i.e., the return type of the method member.

  4. A throws/rethrows clause, i.e., an indication of the ability of a member method to raise exceptions.

Members can be as follows:

  • Static members that are not part of class instances, and can be accessed by using a qualified name notation (see Names) anywhere the class name or the interface name is accessible; and

  • Non-static, or instance members that belong to any instance of the class.

All names in both static and non-static class declaration scopes (see Scopes) must be unique, i.e., fields and methods cannot have the same name.


9.4. Access Modifiers

Access modifiers define how a class member or a constructor can be accessed. Accessibility in ArkTS can be of the following kinds:

  • private,

  • internal,

  • protected, or

  • public.

The desired accessibility of class members and constructors can be explicitly specified by the corresponding access modifiers:

accessModifier:
    'private'
    | 'internal'
    | 'protected'
    | 'public'
    ;

If no explicit modifier is provided, then a class member or a constructor is implicitly considered public by default.


9.4.1. Private Access Modifier

The modifier private indicates that a class member or a constructor is accessible within its declaring class, i.e., a private member or constructor m declared in a class C can be accessed only within the class body of C:

 1 class C {
 2   private count: number
 3   getCount(): number {
 4     return this.count // ok
 5   }
 6 }
 7
 8 function increment(c: C) {
 9   c.count++ // compile-time error – 'count' is private
10 }

9.4.2. Internal Access Modifier

The modifier internal is described in the chapter Experimental Features (see Internal Access Modifier).


9.4.3. Protected Access Modifier

The modifier protected indicates that a class member or a constructor is accessible only within its declaring class and the classes derived from that declaring class. A protected member M declared in a class C can be accessed only within the class body of C or of a class derived from C:

 1 class C {
 2   protected count: number
 3    getCount(): number {
 4      return this.count // ok
 5    }
 6 }
 7
 8 class D extends C {
 9   increment() {
10     this.count++ // ok, D is derived from C
11   }
12 }
13
14 function increment(c: C) {
15   c.count++ // compile-time error – 'count' is not accessible
16 }

9.4.4. Public Access Modifier

The modifier public indicates that a class member or a constructor can be accessed everywhere, provided that the member or the constructor belongs to a type that is also accessible.


9.5. Field Declarations

Field declarations represent data members in class instances.

classFieldDeclaration:
    fieldModifier* variableDeclaration
    ;

fieldModifier:
    'static' | 'readonly'
    ;

A compile-time error occurs if:

  • The same field modifier is used more than once in a field declaration.

  • The name of a field declared in the body of a class declaration is already used for another field or method in the same declaration.

A field declared by a class with a certain name hides any accessible declaration of fields if they have the same name in superclasses and superinterfaces of the class.

If a hidden field is static, then it can be accessed with the qualification of a superclass or of a superinterface, or with a field access expression with the keyword super (see Field Access Expressions).

A class can access all non-private fields of a superclass and superinterfaces from its direct superclass and direct superinterfaces if such non-private fields are both:

  • Accessible (see Scopes) to code in the class; and

  • Not hidden by a declaration in the class.

A class can inherit more than one field or property with the same name from its superinterfaces, or from both its superclass and superinterfaces. However, an attempt to refer to such a field or property by its simple name within the body of the class causes a compile-time error.

The same field or property declaration can be inherited from an interface in more than one way. In that case, the field or property is considered to be inherited only once (and thus, referring to it by its simple name causes no ambiguity).


9.5.1. Static Fields

There are two categories of class or interface fields:

  • Class, or static fields

Static fields are declared with the static modifier. They are not parts of class instances. There is one copy of a static field, irrespective of how many instances of that class (even if zero) are eventually created.

Static fields are accessed using a qualified name notation (where the class or interface name is used as a qualifier, see Names) in any place where the class or interface name is accessible.

  • Instance, or non-static fields.

Instance fields belong to each instance of the class. An instance field is created for, and associated with a newly-created instance of a class or its superclass. Instance fields are accessible via the name of the instance.


9.5.2. Readonly (Constant) Fields

A field that has the modifier readonly is a readonly field. Changing the value of a readonly field after initialization is not allowed.

Both static and non-static fields can be declared readonly fields.

A static readonly field must be initialized as follows:

  • By using a field initializer, or

  • As a result of a class initializer (see Class Initializer).

Otherwise, a compile-time error occurs.

A non-static readonly field must be initialized as a result of execution of any class constructor. Otherwise, a compile-time error occurs.


9.5.3. Field Initialization

An initializer in a non-static field declaration has the semantics of an assignment (see Assignment) to the declared variable.

The following rules apply to an initializer in a static field declaration:

  • A compile-time error occurs if the initializer uses the keywords this or super while calling a method (see Method Call Expression) or accessing a field (see Field Access Expressions).

  • The initializer is evaluated, and the assignment is performed only once when the class is initialized at runtime.

Note: Constant fields are initialized before all other static fields.

Constant fields initialization never uses default values (see Default Values for Types).

In a non-static field declaration, an initializer is evaluated at runtime. Its assignment is performed each time an instance of the class is created. The initializer can use the following keywords:

  • this to access the current object methods or fields, or to refer to the current object;

  • super to access the methods declared in a superclass;

Additional restrictions (as specified in Exceptions and Errors Inside Field Initializers) apply to variable initializers that refer to fields which cannot be initialized yet.

References to a field (even if the field is in the scope) can be restricted. The rules applying to the restrictions on forward references to fields (if the reference textually precedes the field declaration) and self-references (if the field is used within its own initializer) are provided below.

A compile-time error occurs in a reference to a static field f declared in class or interface C if:

  • Such a reference is used in C’s static initializer (see Class Initializer) or static field initializer (see Field Initialization);

  • Such a reference is used before the declaration of f, or within f’s own declaration initializer;

  • No such reference is present on the left-hand side of an assignment expression (see Assignment);

  • C is the innermost class or interface that encloses such a reference.

A compile-time error occurs in a reference to a non-static field f declared in class C if such reference is:

  • Used in the non-static field initializer of C;

  • Used before the declaration of f, or within f’s own declaration initializer;

  • Not present on the left-hand side of an assignment expression (see Assignment);

  • Enclosed in C that is the innermost class or interface.


9.6. Method Declarations

Methods declare executable code that can be called:

classMethodDeclaration:
    methodOverloadSignature*
    methodModifier* typeParameters? identifier signature block?
    ;

methodModifier:
    'abstract'
    | 'static'
    | 'final'
    | 'override'
    | 'native'
    ;

Overloading signature of a method allows calling a method in different ways.

The identifier of classMethodDeclaration is the method name that can be used to refer to a method (see Method Call Expression).

A compile-time error occurs if:

  • A method modifier appears more than once in a method declaration.

  • The body of a class declaration declares a method but the name of that method is already used for a field in the same declaration.

  • The body of a class declaration declares two same-name methods with override-equivalent signatures (see Override-Equivalent Signatures) as members of that body of a class declaration.


9.6.1. Class (Static) Methods

A method declared with the modifier static is a class method. Another name for class methods is static methods.

A compile-time error occurs if:

  • A method declaration contains another modifier (abstract, final, or override) along with the modifier static.

  • The header or body of a class method includes the name of a type parameter of the surrounding declaration.

Class methods are always called without reference to a particular object. As a result, a compile-time error occurs if keywords this or super are used inside a static method.


9.6.2. Instance Methods

A method that is not declared static is called non-static method, or an instance method.

An instance method is always called with respect to an object that becomes the current object the keyword this refers to during the execution of the method body.


9.6.3. Abstract Methods

An abstract method declaration introduces the method as a member along with its signature but without an implementation. An abstract method is declared with the modifier abstract in its declaration.

Non-abstract methods can be referred to as concrete methods.

A compile-time error occurs if:

  • An abstract method is declared private.

  • A method declaration contains another modifier (static, final, or native) along with the modifier abstract.

  • The abstract method m declaration does not appear directly within an abstract class A.

  • Any non-abstract subclass of A (see Abstract Classes) does not provide an implementation for m.

An abstract method can be overridden by another abstract method declaration provided by an abstract subclass.

A compile-time error occurs if an abstract method overrides a non-abstract instance method.


9.6.4. Final Methods

Final methods are described in the chapter Experimental Features (see Native Methods).


9.6.5. Overriding Methods

The override modifier indicates that an instance method in a superclass is overridden by the corresponding instance method from a subclass (see Overriding by Instance Methods).

The usage of the modifier override is optional but strongly recommended as it makes the overriding explicit.

A compile-time error occurs if:

  • A method marked with the modifier override does not override a method from a superclass.

  • A method declaration that contains the modifier override also contains the modifiers abstract or static.

If the signature of the overridden method contains parameters with default values (see Optional Parameters), then the overriding method always uses the default parameter values of the overridden method.

A compile-time error occurs if a parameter in the overriding method has a default value.

See Overriding by Instance Methods for the specific rules of overriding.


9.6.6. Native Methods

Native methods are described in the chapter Experimental Features (see Native Methods).


9.6.7. Method Overload Signatures

ArkTS allows specifying a method that has several overload signatures, i.e., several method headers that have the same name followed by one implementation body.

methodOverloadSignature:
    methodModifier* identifier signature
    ;

A compile-time error occurs if the method implementation is not present, or does not immediately follow the declaration.

A call of a method with overload signatures is always a call of the implementation method.

The example below has one overload signature parameterless; the other two have one parameter each:

 1 class C {
 2     foo(): void           // 1st signature
 3     foo(x: string): void  // 2nd signature
 4     foo(x?: string): void // implementation signature
 5     {
 6         console.log(x)
 7     }
 8 }
 9 let c = new C()
10 c.foo()          // ok, call fits 1st and 3rd signatures
11 c.foo("aa")      // ok, call fits 2nd and 3rd signatures
12 c.foo(undefined) // ok, call fits the 3rd signature

The call c.foo() is executed as a call of the implementation method with the undefined argument. The call c.foo(x) is executed as a call of the implementation method with an argument.

Overload signature compatibility requirements are described in Overload Signature Compatibility.

In addition, a compile-time error occurs if not all of the following requirements are met:

  • Overload signatures and the implementation method have the same access modifier.

  • All overload signatures and the implementation method are static or non-static.

  • All overload signatures and the implementation method are final or non-final.

  • Overload signatures are not native (however, native implementation method is allowed).

  • Overload signatures are not abstract.


9.6.8. Method Body

A method body is a block of code that implements a method. A semicolon, or an empty body (i.e., no body at all) indicate the absence of the implementation.

An abstract or native method must have an empty body.

In particular, a compile-time error occurs if:

  • The body of an abstract or native method declaration is a block.

  • A method declaration is neither abstract nor native, but its body is empty, or is a semicolon.

See return Statements for the rules that apply to return statements in a method body.

A compile-time error occurs if a method is declared to have a return type, but its body can complete normally (see Normal and Abrupt Statement Execution).


9.6.9. Inheritance

Class C inherits from its direct superclass all concrete methods m (both static and instance) that meet all of the following requirements:

  • m is a member of the direct superclass of C;

  • m is public, protected, or internal in the same package as C;

  • No signature of a method declared in C is compatible with the signature of m (see Compatible Signature).

Class C inherits from its direct superclass and direct superinterfaces all abstract and default methods m (see Default Method Declarations) that meet the following requirements:

  • m is a member of D, which is a direct superclass or direct superinterface of C;

  • m is public, protected, or internal in the same package as C;

  • No method declared in C has a signature that is compatible with the signature of m (see Compatible Signature);

  • No signature of a concrete method inherited by C from its direct superclass is compatible with the signature of m (see Compatible Signature);

  • No method \(m'\) that is a member of D’, which is a direct superclass or direct superinterface of C (while \(m'\) is distinct from m, and \(D'\) from D), overrides the declaration of the method m from \(D'\) (see Overriding by Instance Methods for class method overriding, and Overriding by Instance Methods for interface method overriding).

No class can inherit private or static methods from its superinterfaces.


9.6.10. Overriding by Instance Methods

The instance method mC (inherited by, or declared in class C) overrides another method mA (declared in class A) if all the following requirements are met:

  • C is a subclass of A;

  • C does not inherit mA;

  • The signature of mC is compatible with the signature of mA (see Compatible Signature);

—and if one of the following is also true:

  • mA is public;

  • mA is protected; or

  • mA is internal in the same package as C while:

    • Either C declares mC; or

    • mA is a member of the direct superclass of C;

  • mA is declared with package access, and mC overrides:

    • mA from a superclass of C; or

    • method \(m'\) from C (while \(m'\) is distinct from both mC and mA, i.e., \(m'\) overrides mA from a superclass of C).

Non-abstract mC implements mA from C if it overrides an abstract method mA.

An instance method mC (inherited by, or declared in class C) overrides another method mI (declared in interface I) from C if all of the following requirements are met:

  • I is a superinterface of C;

  • mI is not static;

  • C does not inherit mI;

  • The signature of mC is a subsignature of the signature of mI (see Override-Equivalent Signatures); and

  • mI is public.

A method call expression (see Method Call Expression) containing the keyword super can be used to call an overridden method.

Among the methods that override each other, return types can vary if they are reference types.

The specialization of return type to a subtype (i.e., covariant returns) is based on the concept of return-type-substitutability. For example, the method declaration d1 with return type R1 is return-type-substitutable for another method d2 with return type R2 if:

  • R1 is a primitive type (R2 is identical to R1); or

  • R1 is a reference type (R1 adapted to type parameters of d2 is a subtype of R2).


9.6.11. Hiding by Class Methods

A static method m declared in, or inherited by a class C hides any method \(m'\) (where the signature of m is a subsignature of the signature of \(m'\) as described in Override-Equivalent Signatures) in its superclasses and superinterfaces.

A hidden method is not directly accessible (see Scopes) to code in C. However, a hidden method can be accessed by using a qualified name, or a method call expression (see Method Call Expression) that contains the keyword super or a cast to a superclass type.

A compile-time error occurs if a static method hides an instance method.


9.6.12. Requirements in Overriding and Hiding

The method declaration d1 with return type R1 can override or hide the declaration of another method d2 with return type R2 if d1 is return-type-substitutable for d2 (see Requirements in Overriding and Hiding and Overriding by Instance Methods). Otherwise, a compile-time error occurs.

A method that overrides or hides another method (including the methods that implement abstract methods defined in interfaces) cannot change ‘throws’ or ‘rethrows’ clauses of the overridden or hidden method.

A compile-time error occurs if a type declaration T has a member method m1, but there is also a method m2 declared in T or a supertype of T, for which all of the following requirements are met:

The access modifier of an overriding or hiding method must provide no less access than was provided in the overridden or hidden method.

A compile-time error occurs if:

  • The overridden or hidden method is public, and the overriding or hiding method is not public.

  • The overridden or hidden method is protected, and the overriding or hiding method is not protected or public.

  • The overridden or hidden method has internal access, and the overriding or hiding method is private.


9.6.13. Inheriting Methods with Override-Equivalent Signatures

A class can inherit multiple methods with override-equivalent signatures (see Override-Equivalent Signatures).

A compile-time error occurs if a class C inherits the following:

  • Concrete method whose signature is override-equivalent to another method that C inherited; or

  • Default method whose signature is override-equivalent to another method that C inherited, if there is no abstract method, declared in a superclass of C and inherited by C, that is override-equivalent to both methods.

An abstract class can inherit all the methods, assuming that a set of override-equivalent methods consists of at least one abstract method, and zero or more default methods.

A compile-time error occurs if one of the inherited methods is not return-type-substitutable for every other inherited method (except ‘throws’ and ‘rethrows’ clauses that cause no error in this case).

The same method declaration can be inherited from an interface in a number of ways, causing no compile-time error on its own.


9.7. Accessor Declarations

Accessors are often used instead of fields to add additional control for operations of getting or setting a field value. An accessor can be either a getter or a setter.

classAccessorDeclaration:
    accessorModifier
    ( 'get' identifier '(' ')' returnType block?
    | 'set' identifier '(' parameter ')' block?
    )
    ;

accessorModifier:
    'abstract'
    | 'static'
    | 'final'
    | 'override'
    ;

Accessor modifiers are a subset of method modifiers. The allowed accessor modifiers have exactly the same meaning as the corresponding method modifiers. See Abstract Methods for the modifier abstract, Class (Static) Methods for the modifier static, Final Methods for the modifier final, and Overriding Methods for the modifier override.

1 class Person {
2   private _age: number = 0
3   get age(): number { return this._age }
4   set age(a: number) {
5     if (a < 0) { throw new Error("wrong age") }
6     this._age = a
7   }
8 }

Each get-accessor (getter) must have neither parameters nor an explicit return type. Each set-accessor (setter) must have a single parameter and no return value. The use of getters and setters looks the same as the use of fields:

 1 class Person {
 2   private _age: number = 0
 3   get age(): number { return this._age }
 4   set age(a: number) {
 5     if (a < 0) { throw new Error("wrong age") }
 6     this._age = a
 7   }
 8 }
 9
10 let p = new Person()
11 p.age = 25        // setter is called
12 if (p.age > 30) { // getter is called
13   // do something
14 }

A class can define a getter, a setter, or both with the same name. If both a getter and a setter with a particular name are defined, then both must have the same accessor modifiers. Otherwise, a compile-time error occurs.

Accessors can be implemented by using a private field to store its value (as in the example above).

1 class Person {
2   name: string = ""
3   surname: string = ""
4   get fullName(): string {
5     return this.surname + " " + this.name
6   }
7 }

9.8. Class Initializer

When a class is initialized, the class initializer declared in the class is executed along with all class initializers of all superclasses. The order of execution is from the top superclass to the current class. Class initializers (along with field initializers for static fields as described in Field Initialization) ensure that all static fields receive their initial values before the first use.

1 classInitializer
2     : 'static' block
3     ;

A compile-time error occurs if a class initializer contains:

Restrictions of class initializers’ ability to refer to static fields (even those within the scope) are specified in Exceptions and Errors Inside Field Initializers. Class initializers cannot throw exceptions as they are effectively non-throwing functions (see Non-Throwing Functions).


9.9. Constructor Declaration

Constructors are used to initialize objects that are instances of class.

A constructor declaration starts with the keyword constructor, and has no name. In any other syntactical aspect, a constructor declaration is similar to a method declaration with no return type.

constructorDeclaration:
    constructorOverloadSignature*
    'constructor' '(' parameterList? ')' throwMark? constructorBody
    ;

throwMark:
    'throws'
    | 'rethrows'
    ;

Constructors are called by the following:

Access to constructors is governed by access modifiers (see Access Modifiers and Scopes). Declaring a constructor inaccessible can prevent class instantiation.

A compile-time error occurs if two constructors in a class are declared, and have identical signatures.

See Throwing Functions for ‘throws’ mark, and Rethrowing Functions for ‘rethrows’ mark.


9.9.1. Formal Parameters

The syntax and semantics of a constructor’s formal parameters are identical to those of a method.

9.9.2. Constructor Overload Signatures

ArkTS allows specifying a constructor that can be called in different ways by providing overload signatures, i.e., several constructor headers which are followed by one constructor implementation body.

constructorOverloadSignature:
    accessModifier? 'constructor' signature
    ;

A compile-time error occurs if the constructor implementation is not present, or does not immediately follow the declaration.

A call of a constructor with overload signature is always a call of the constructor implementation body.

The example below has one overload signature parameterless, and others have one parameter each:

 1 class C {
 2     constructor()           // 1st signature
 3     constructor(x: string)  // 2nd signature
 4     constructor(x?: string) // 3rd - implementation signature
 5     {
 6         console.log(x)
 7     }
 8 }
 9 new C()          // ok, fits the 1st and 3rd signatures
10 new C("aa")      // ok, fits the 2nd and 3rd signatures
11 new C(undefined) // ok, fits 3rd signature

The new expression (see New Expressions) new C() leads to a call of the constructor implementation with the argument undefined. The new C(x) creates an object calling constructor implementation with ‘x’ as an argument.

Overload signature compatibility requirements are described in Overload Signature Compatibility.

A compile-time error occurs if at least two different overload signatures or implementation signatures have different access modifiers.

1 class Incorrect {
2     // Constructors have different access modifiers
3     private constructor()             // private 1st signature
4     protected constructor(x: string)  // protected 2nd signature
5     constructor(x?: string)           // public 3rd - implementation signature
6     {}
7 }

9.9.3. Constructor Body

The first statement in a constructor body can be an explicit call of another same-class constructor, or of the direct superclass (see Explicit Constructor Call):

constructorBody:
    '{' constructorCall? statement* '}'
    ;

constructorCall:
    'this' arguments
    | 'super' arguments
    ;
 1 class Point {
 2   x: number
 3   y: number
 4   constructor(x: number, y: number) {
 5     this.x = x
 6     this.y = y
 7   }
 8 }
 9
10 class ColoredPoint extends Point {
11   static readonly WHITE = 0
12   static readonly BLACK = 1
13   color: number
14   constructor(x: number, y: number, color: number) {
15     super(x, y) // calls base class constructor
16     this.color = color
17   }
18 }

A compile-time error occurs if a constructor calls itself, directly or indirectly, through a series of one or more explicit constructor calls using this.

The constructor body must implicitly begin with a superclass constructor call ‘super()’ (call of the constructor’s direct superclass that takes no argument), if the constructor body does not begin with an explicit constructor call. The constructor so declared is a part of the primordial class Object.

A constructor body looks like a method body (see Method Body), except that explicit constructor calls are possible, and explicit return of a value (see return Statements) is prohibited. However, a return statement without an expression can be used in a constructor body.

A constructor body must not use fields of a created object before the fields are initialized; this cannot be passed as an argument until each object field receives an initial value. The check can be performed by the compiler that reports a compile-time error if a violation is detected. In difficult corner cases checks must be performed at runtime. The check raises an exception if an attempt to work with a non-initialized object field is detected.


9.9.4. Explicit Constructor Call

There are two kinds of explicit constructor call statements:

  • Alternate constructor calls that begin with the keyword this, and can be prefixed with explicit type arguments (used to call an alternate same-class constructor).

  • Superclass constructor calls (used to call a constructor from the direct superclass) called unqualified superclass constructor calls that begin with the keyword super, and can be prefixed with explicit type arguments.

A compile-time error occurs if the constructor body of an explicit constructor call statement:

  • Refers to any non-static field or instance method; or

  • Uses the keywords this or super in any expression.

An ordinary method call evaluates an alternate constructor call statement left-to-right. The evaluation starts from arguments, proceeds to constructor, and then the constructor is called.

The process of evaluation of a superclass constructor call statement is performed as follows:

  1. If instance i is created, then the following procedure is used to determine i’s immediately enclosing instance with respect to S (if available):

    • If the declaration of S occurs in a static context, then i has no immediately enclosing instance with respect to S.

    • If the superclass constructor call is unqualified, then S must be a local class.

      If S is a local class, then the immediately enclosing type declaration of S is O.

      If n is an integer (\(n\geq{}1\)), and O is the n’th lexically enclosing type declaration of C, then i’s immediately enclosing instance with respect to S is the n’th lexically enclosing instance of this.

  1. After i’s immediately enclosing instance with respect to S (if available) is determined, the evaluation of the superclass constructor call statement continues left-to-right. The arguments to the constructor are evaluated, and then the constructor is called.

  2. If the superclass constructor call statement completes normally after all, then all non-static field initializers of C are executed. I is executed before J if a non-static field initializer I textually precedes another non-static field initializer J.

    Non-static field initializers are executed if the superclass constructor call:

    • Has an explicit constructor call statement; or

    • Is implicit.

    An alternate constructor call does not perform the implicit execution.


9.9.5. Default Constructor

If a class contains no constructor declaration, then a default constructor is implicitly declared. The default constructor has the following form:

  • The access modifier of the default constructor and of the class is the same (if the class has no access modifier, then the default constructor has the internal access (see Scopes).

  • The default constructor has no ‘throws’ or ‘rethrows’ clauses.

  • The body of the default constructor contains a call to the the superclass constructor with no arguments except the primordial class Object. The body of the default constructor for the primordial class Object is empty.

A compile-time error occurs if a default constructor is implicit, but the superclass:

  • Has no accessible constructor without parameters; and

  • Has a constructor without parameters but with ‘throws’ or ‘rethrows’ clauses.

 1// Class declarations without constructors
 2class Object {}
 3class Base {}
 4class Derived extends Base {}
 5
 6
 7// Class declarations with default constructors declared implicitly
 8class Object {
 9  constructor () {} // Empty body - as there is no supercalss
10}
11// Default constructors added
12class Base { constructor () { super () } }
13class Derived extends Base { constructor () { super () } }
14
15// Example of an error case #1
16class A {
17    private constructor () {}
18}
19class B extends A {} // No constructor in B
20// During compialtion of B
21class B extends A { constructor () { super () } } // Default constructor added
22// And it leads to conpile-time error as default constructor calls super()
23// which is private and inaccessible
24
25// Example of an error case #2
26class A {
27    constructor () throws {}
28}
29class B extends A {} // No constructor in B
30// During compialtion of B
31class B extends A { constructor () { super () } } // Default constructor added
32// And it leads to conpile-time error as default constructor is not marked as throws
33// but it call super() which throws

9.10. Local Classes and Interfaces

Local classes and interfaces (see Interfaces) are declared within the body of a function, method, or any block delimited by balanced braces in a group of zero or more statements.

Names of local classes and interfaces are visible only within the scope they are declared in. When declared in a scope, names of local classes and interfaces have access to entities visible within this scope, and capture the entities they use from this scope. Function/method parameters and local variables can be used and thus captured.

A compile-time error occurs if:

  • A local class or interface declaration has access modifier public, protected, or private.

  • A local class or interface declaration members have access modifier public, protected, private, or export.

The example below shows local classes and interfaces in a top-level function:

 1 function foo (parameter: number) {
 2   let local: string = "function local"
 3   interface LocalInterface { // Local interface in a top-level function
 4     method (): void // It has a method
 5     field: string   // and a property
 6   }
 7   class LocalClass implements LocalInterface { // Class implements interface
 8     // Local class in a top-level function
 9     method () { console.log ("Instance field = ", this.field, " par = ", parameter, " loc = ", local) }
10     field: string = "`instance field value`"
11     static method () { console.log ("Static field = ", LocalClass.field) }
12     static field: string = "`class/static field value`"
13   }
14   let lc: LocalInterface  = new LocalClass
15     // Both local types can be freely used in the top-level function scope
16   lc.method()
17   LocalClass.method()
18 }

The example below shows local classes and interfaces in a class method. The algorithm is similar to that in a top-level function. However, the surrounding class members are not accessible from local classes:

 1 class A_class {
 2   field: number = 1234 // Not visible for the local class
 3   method (parameter: number) {
 4     let local: string = "instance local"
 5     interface LocalInterface {
 6        method (): void
 7        field: string
 8     }
 9     class LocalClass implements LocalInterface {
10        method () { console.log ("Instance field = ", this.field, " par = ", parameter, " loc = ", local) }
11        field: string = "`instance field value`"
12        static method () { console.log ("Static field = ", LocalClass.field) }
13        static field: string = "`class/static field value`"
14     }
15     let lc: LocalInterface  = new LocalClass
16     lc.method()
17     LocalClass.method()
18   }
19   static method (parameter: number) {
20     let local: string = "class/static local"
21     interface LocalInterface {
22        method (): void
23        field: string
24     }
25     class LocalClass implements LocalInterface {
26        method () { console.log ("Instance field = ", this.field, " par = ", parameter, " loc = ", local) }
27        field: string = "`instance field value`"
28        static method () { console.log ("Static field = ", LocalClass.field) }
29        static field: string = "`class/static field value`"
30     }
31     let lc: LocalInterface  = new LocalClass
32     lc.method()
33     LocalClass.method()
34   }
35 }