Classes and Interfaces
Classes
Defining Classes
In the Cangjie programming language, the keyword class
is used to define a class. The class definition includes the following parts:
- Optional modifiers.
- Keyword
class
. - Class name, which must be a valid identifier.
- Optional type parameters.
- Optional superclasses or superinterfaces (write them after
<:
and use&
to separate them) that are specified. If a superclass exists, it must be written first. Otherwise, an error is reported during compilation. - Optional generic constraints.
- Class body.
A class can be defined only at the top level of a source file. The syntax for defining a class is as follows:
classDefinition
: classModifierList? 'class' identifier
typeParameters?
('<:' superClassOrInterfaces)?
genericConstraints?
classBody
;
superClassOrInterfaces
: classType ('&' superInterfaces)?
| superInterfaces
;
The following is an example of class definition:
interface I1<X> {}
interface I2 {}
open class A{}
// The following class B inherits class A and implements interface I2.
open class B <: A & I2 {}
/* The following class C declares 1 type parameters U. It inherits B and implements interfaces I1<U> and I2. Also, its type parameters have constraints U <: A */
class C<U> <: B & I1<U> & I2 where U <: A {}
Class Modifiers
In this section, we will introduce the modifiers that can be used to define classes.
Access Modifiers
Classes can be modified by all access modifiers. The default accessibility is internal
. For details, see Access Modifiers in [Packages and Modules].
Inheritance Modifiers
-
open
: If a class is modified byopen
, other classes can inherit this class./* The following class is declared with modifier `open`, indicating that it can be inherited. */ open class C1 { func foo(): Unit { return } } class C2 <: C1 {}
Note that:
A non-abstract class that is not modified by
open
cannot be inherited by any class.
-
sealed
: The class can be inherited only in the package where the class is defined.-
sealed
can only modify abstract classes. -
sealed
contains the semantics ofpublic
. Therefore,public
is optional when the sealed class is defined. -
The subclasses of
sealed
can be non-sealed
classes, and can still be modified byopen
orsealed
, or do not use any inheritance modifier. If the subclasses ofsealed
are modified by bothpublic
andopen
, the subclasses can be inherited outside the package. -
The subclasses of
sealed
may not be modified bypublic
.
// package A package A public sealed class C1 {} // OK sealed class C2 {} // OK, 'public' is optional when 'sealed' is used class S1 <: C1 {} // OK public open class S2 <: C1 {} // OK public sealed class S3 <: C1 {} // OK open class S4 <: C1 {} // OK // package B package B import A.* class SS1 <: S2 {} // OK class SS2 <: S3 {} // Error, S3 is sealed class, cannot be inherited here.
-
Note that:
A non-abstract class that is not modified by
open
orsealed
cannot be inherited by any class.
Abstract Class Modifiers
-
abstract
: belongs to the abstract class. Different from an ordinary class, an abstract class can define ordinary functions and declare abstract functions. Only the class modified by this modifier is an abstract class. A function without a function body is called an abstract function.Note that:
- The
abstract
modifier of the abstract class contains the semantics that can be inherited. Therefore, theopen
modifier is optional when the abstract class is defined. You can also use thesealed
modifier for the abstract class, indicating that the abstract class can be inherited only in its package. - Do not define abstract functions of
private
in abstract classes. - Instances cannot be created for abstract classes.
- The abstract subclasses of an abstract class are allowed not to implement abstract functions in the class.
- The non-abstract subclasses of an abstract class must implement all abstract functions in the class.
- The
Class Inheritance
Classes support only single inheritance. A class specifies the direct superclass of the current class with <: superClass
(The superclass is a defined class).
The following example shows the syntax of how the C2
class inherits the C1
class:
open class C1 {}
class C2 <: C1 {}
The superclass can be a generic class. You only need to provide a valid type during inheritance. The following example shows the non-generic class C2
and the generic class C3
inherit the generic class C1
:
open class C1<T> {}
class C2 <: C1<Int32> {}
class C3<U> <: C1<U> {}
Except for `Object`, every class has its superclass. If a class has no superclass defined, its superclass is `Object` by default.
```cangjie
class Empty {} // Inherit from Object implicitly: class Empty <: Object {}
Classes support only single inheritance. The syntax in the following example will cause a compilation error.
open class C1 {}
open class C2 {}
class C3 <: C1 & C2 {} // Error: Multiple inheritance is not supported.
When a class inherits another class, the latter is called a superclass, and the former is called a subclass.
open class C1 {} // C1 is superclass
class C2 <: C1 {} // C2 is subclass
A subclass inherits all members of its superclass, except for private members and constructors.
A class can directly access the members of its superclass. However, during overwriting, an overwritten instance member of the superclass cannot be directly accessed by its name. In this case, you can use super
to specify the member (super
points to the direct superclass of the current class object) or create an object and access the member through the object.
Interface Implementation
A class can implement one or more interfaces. You can use <: I1 & I2 &... & Ik
to declare the interfaces to be implemented by the current class. Multiple interfaces are separated by &
. If a superclass is also specified by the current class, place the interfaces after the superclass. Example:
interface I1 {}
interface I2 {}
// Class C1 implements interface I1
open class C1 <: I1 {}
// Class C2 inherits class C1 and implements interface I1, I2.
class C2 <: C1 & I1 & I2 {}
The interfaces can also be generic. In this case, valid type parameters need to be provided when the generic interfaces are implemented. Example:
interface I1<T> {}
interface I2<U> {}
class C1 <: I1<Int32> & I2<Bool> {}
class C2<K, V> <: I1<V> & I2<K> {}
When a type implements an interface, a non-generic interface cannot be directly implemented multiple times, while a generic interface cannot be directly implemented multiple times with the same type parameters. For example:
interface I1 {}
class C1 <: I1 & I1 {} // error
interface I2<T> {}
class C2 <: I2<Int32> & I2<Int32> {} // error
class C3<T> <: I2<T> & I2<Int32> {} // ok
If the type parameter Int32
is used when the generic class C3
is defined, two interfaces of the same type are implemented. As a result, the compiler reports an error at the position where the type is used.
interface I1<T> {}
open class C3<T> <: I1<T> & I1<Int32> {} // ok
var a: C3<Int32> // error
var b = C3<Int32>() // error
class C4 <: C3<Int32> {}// error
For details about the interfaces, see chapter [Interfaces].
Class Body
Class body indicates the content included in the current class. It is enclosed in braces and contains the following content:
- Optional static initializers
- Optional primary constructors
- Optional constructors
- Definitions of optional member variables
- Definitions or declarations of optional member functions and member operator functions
- Definitions or declarations of optional member properties
- Optional macro call expressions
- Optional class finalizers
The syntax of the class body is defined as follows:
classBody
: '{'
classMemberDeclaration*
classPrimaryInit?
classMemberDeclaration*
'}'
;
classMemberDeclaration
: classInit
| staticInit
| variableDeclaration
| functionDefinition
| operatorFunctionDefinition
| macroExpression
| propertyDefinition
| classFinalizer
;
The syntax definition of the preceding class body includes the following content:
staticInit
: definition of a static initializer. Only one static initializer can be defined for a class.
classPrimaryInit
: definition of the primary constructor. Only one can be defined for a class.
classInit
: definition of the init
constructor.
variableDeclaration
: declaration of a member variable.
operatorFunctionDefinition
: definition of an overloaded member function of an operator.
macroExpression
: macro call expression. After the macro is expanded, it must comply with the syntax definition of classMemberDeclaration
.
propertyDefinition
: property definition.
classFinalizer
: definition of a class finalizer.
The definitions or declarations introduced in the class body are members of the class. For details, see chapter [Members of a Class].
Members of a Class
The members of a class are as follows:
- Members inherited from the superclass (if any).
- If a class implements an interface, its members also include those inherited from the interface.
- Members declared or defined in the class body, including static initializers, primary constructor,
init
constructor, static member variables, instance member variables, static member functions, instance member functions, static member properties, and instance member properties.
Class members can be categorized by different dimensions:
Members can be classified into static members and instance members based on whether they are modified by static
. Static members refer to members that can be accessed without instantiating class objects. Instance members refer to members that can be accessed through objects only after class objects are instantiated.
Members are classified into static initializers, constructors, member functions, member variables, and member properties.
Note that: All static members cannot be accessed by their names.
Constructors
In the Cangjie programming language, there are two types of constructors: primary constructor and init
constructor (constructor for short).
Primary Constructor
The syntax of the primary constructor is defined as follows:
classPrimaryInit
: classNonStaticMemberModifier? className '(' classPrimaryInitParamLists? ')'
'{'
superCallExression?
( expression
| variableDeclaration
| functionDefinition)*
'}'
;
className
: identifier
;
classPrimaryInitParamLists
: unnamedParameterList (',' namedParameterList)? (',' classNamedInitParamList)?
| unnamedParameterList (',' classUnnamedInitParamList)?
(',' classNamedInitParamList)?
| classUnnamedInitParamList (',' classNamedInitParamList)?
| namedParameterList (',' classNamedInitParamList)?
| classNamedInitParamList
;
classUnnamedInitParamList
: classUnnamedInitParam (',' classUnnamedInitParam)*
;
classNamedInitParamList
: classNamedInitParam (',' classNamedInitParam)*
;
classUnnamedInitParam
: classNonStaticMemberModifier? ('let'|'var') identifier ':' type
;
classNamedInitParam
: classNonStaticMemberModifier? ('let'|'var') identifier'!' ':' type ('=' expression)?
;
classNonStaticMemberModifier
: 'public'
| 'protected'
| 'internal'
| 'private'
;
The definition of the primary constructor consists of the following parts:
1. Modifier: optional. The primary constructor can be modified by public
, protected
, or private
. If none of them is used, the primary constructor is visible in the package. For details, see [Access Modifiers].
2.Primary constructor name: same as the type name. Do not use the func
keyword before the primary constructor name.
3. Parameter list: Different from the init
constructor, the primary constructor has two types of parameters: ordinary parameters and member variable parameters. The syntax and semantics of ordinary parameters are the same as those of parameters in the function definition.
Member variable parameters are introduced to reduce code redundancy. The definition of a member variable parameter includes the definition of a parameter and a member variable. In addition, it indicates the semantics of assigning a value to a member variable through a parameter. Omitted definitions and expressions are automatically generated by the compiler.
-
The syntax of member variable parameters is the same as that of member variable definition. In addition,
!
can be used to indicate whether a member variable parameter is a named parameter. -
The modifiers of member variables include
public
,protected
,private
. For details, see [Access Modifiers]. -
Member variable parameters only support instance member variables. That is, they cannot be modified by
static
. -
A member variable parameter cannot have the same name as a member variable outside the primary constructor.
-
A member variable parameter can have no initial value. This is because the compiler generates a corresponding constructor for the primary constructor and assigns values to member variables in the constructor body.
-
A member variable parameter can also have initial value. The initial value is used only as the default value of constructor parameter. The initial value expression of a member variable parameter can reference other parameters or member variables (excluding instance member variables defined outside the primary constructor) that have been defined before the member variable is defined, but cannot modify the values of these parameters and member variables. Note that the initial value of the member variable parameter is valid only in the primary constructor and is not included in the member variable definition.
-
Member variable parameters cannot be followed by ordinary parameters. In addition, the parameter sequence must comply with that defined in the function. Parameters of named variables cannot be followed by non-named parameters.
4. Primary constructor body: If the constructor of the superclass is explicitly called, the first expression in the function body must be the expression that calls the constructor of the superclass. In addition, this
cannot be used to call other constructors in the same class as the primary constructor. After the superclass constructor is called, expressions, local variable declarations, and local function definitions can be written in the primary constructor body. The declarations, definitions, and expressions must meet the rules for using this
and super
in the init
constructor. For details, see [init Constructor].
The following is an example of the primary constructor definition:
class Test{
static let counter: Int64 = 3
let name: String = "afdoaidfad"
private Test(
name: String, // regular parameter
annotation!: String = "nnn", // regular parameter
var width!: Int64 = 1, // member variable parameter with initial value
private var length!: Int64, // member variable parameter
private var height!: Int64 = 3 // member variable parameter
) {
}
}
When the primary constructor is defined, ordinary parameters are not allowed after the parameters of member variables. The following is an example:
class Test{
static let counter: Int64 = 3
let name: String = "afdoaidfad"
private Test(
name: String, // regular parameter
annotation!: String = "nnn", // regular parameter
var width!: Int64 = 1, // member variable parameter with initial value
length!: Int64 // Error: regular parameters cannot be after member variable parameters
) {
}
}
The primary constructor is the syntactic sugar of the init
constructor. The compiler automatically generates the definitions of the constructor and member variables corresponding to the primary constructor. The constructor is automatically generated as follows:
- Its modifier is the same as that of the primary constructor.
- The sequence of parameters from left to right is the same as that declared in the parameter list of the primary constructor.
- The form of the constructor body is as follows:
- Values are assigned to member variables in sequence. The syntax format is
this.x = x
, wherex
is the member variable name. - Code in the primary constructor body.
- Values are assigned to member variables in sequence. The syntax format is
open class A<X> {
A(protected var x: Int64, protected var y: X) {
this.x = x
this.y = y
}
}
class B<X> <: A<X> {
B( // primary constructor, it's name is the same as the class
x: Int64, // regular parameter
y: X, // regular parameter
v!: Int64 = 1, // regular parameter
private var z!: Int64 = v // member variable parameter
) {
super(x, y)
}
/* The corresponding init constructor with primary constructor auto-generated
by compiler.
private var z: Int64 // auto generated member variable definition
init( x: Int64,
y: X,
v!: Int64 = 1,
z!: Int64 = v) { // auto generated named parameter definition
super(x, y)
this.z = z // auto generated assign expression of member variable
}
*/
}
A class can define only one primary constructor. In addition to the primary constructor, other constructors can be defined as usual, but other constructors and the constructor corresponding to the primary constructor must be overloaded.
init
Constructor
The constructor is specified using the init
keyword and cannot contain the func
keyword. The return type cannot be explicitly defined for the constructor, and the function body is required. The return type of the constructor is Unit
.
The syntax of the constructor is as follows:
Init
: nonSMemberModifier? 'init' '(' InitParamLists? ')'
'{'
(superCallExression | initCallExpression)?
( expression
| variableDeclaration
| functionDefinition)*
'}'
;
InitParamLists
: unnamedParameterList (',' namedParameterList)?
| namedParameterList
;
You can add an access modifier before init
to limit the access scope of the constructor. For details, see [Access Modifiers].
When an object of a class is constructed, the constructor of this class is called. If there is no accessible constructor that matches the parameter type, an error is reported during compilation.
In a class, you can provide multiple init
constructors for the class. These constructors must meet the requirements of function overloading. For details, see [Function Overloading].
class C {
init() {}
init(name: String, age: Int32) {}
}
The constructor called when creating an instance of a class executes the expressions in the class in the following sequence:
-
Initialize the variables that are defined outside the primary constructor and have default values.
-
If the constructor does not explicitly call the superclass constructor or other constructors of the class, the parameterless constructor
super()
constructor of the superclass is called. If the superclass does not have a parameterless constructor, an error is reported. -
Execute the code in the constructor body.
If a class does not define a primary constructor or an init
constructor, it attempts to generate a parameterless constructor (public
-modified). If the superclass does not have a parameterless constructor or the instance member variables of the class do not have initial values, an error is reported during compilation.
The rules for using constructors, this
, and super
are as follows:
- Do not use the instance member variable
this.variableName
and its syntactic sugarsvariableName
andsuper.variableName
as the default values of constructor parameters. - The
init
constructor can call either the superclass constructor or other constructors of the current class, but only one of them can be called. If this function is called, it must be called at the first expression in the constructor body. No expression or declaration is allowed before this function is called. - If the constructor does not explicitly call other constructors or the superclass constructor, the compiler inserts the parameterless constructor of the direct superclass at the beginning of the constructor body. If the superclass does not have a parameterless constructor, a compilation error is reported.
- After the constructor of the superclass or other constructors of the class are called in the constructor body,
super.x
can be used to access the instance member variablex
of the superclass. - If the constructor does not explicitly call other constructors, ensure that all instance member variables declared by the class are initialized before return. Otherwise, an error is reported during compilation.
- Do not call instance member functions or instance member properties in the constructors of a class that can be inherited.
- Do not allow
this
escape in the constructors of a class that can be inherited. - Before all instance member variables are initialized, constructors are forbidden to use implicit parameter transfer, functions or
lambda
that capturethis
,super.f
to access the instance member methodf
of the superclass, or independentthis
expressions. However,this.x
or its syntaxx
can be used to access the initialized member variablex
. - Do not call the constructors of this class through
this
outside the constructor body. - Circular dependency between constructors is not allowed. Otherwise, a compilation error will be reported.
var b: Int64 = 1
class A {
var a: Int64 = 1
var b: ()->Int64 = { 3 } // OK
/* Cannot use lambda which captured `this` before all of the instance member
variables are initialized. */
var c: ()->Int64 = { a } // Error
/* Cannot use function which captured `this` before all of the instance member variables are initialized. */
var d: ()->Int64 = f // Error
var e: Int64 = a + 1 // OK
func f(): Int64 {
return a
}
}
class B {
var a: Int64 = 1
var b: ()->Int64
init() {
b = { 3 }
}
init(p: Int64) {
this()
b = { this.a } // OK
b = f // OK
}
func f(): Int64 {
return a
}
}
var globalVar: C = C()
func f(c: C) {
globalVar = c
}
open class C {
init() {
globalVar = this // Error, `this` cannot escape out of constructors of `open` class
f(this) // Error, `this` cannot escape out of constructors of `open` class
m() // Error: calling instance function is forbidden in constructors of `open` class
}
func m() {}
}
Static Initializers
Static variables in a class or structure can also be initialized by using assignment expressions in a static initializer. Static initializers cannot be used in enumerations and interfaces.
The syntax of a static initializer is as follows:
staticInit
: 'static' 'init' '(' ')'
'{'
expressionOrDeclarations?
'}'
;
The rules of a static initializer are as follows:
-
The static initializer is automatically called, but cannot be explicitly called by developers.
-
The static initializer is called when the package to which it belongs is loaded, which is similar to the initialization expression of a static variable.
-
A class or structure can have only one static initializer.
-
For a non-generic class or structure, the static initializer is called only once.
-
For a generic class or structure, the static initializer is called only once for instantiation of each different type.
- Note that the static initializer will not be called at all if the generic class or structure is not instantiated.
-
The static initializer is called after all static member variables in the class or structure have been directly initialized, just as the constructor is called after all instance fields have been directly initialized.
-
This means that further declared static member variables can be referenced in the static initializer.
-
This also means that the static initializer can be located anywhere in the class or structure, which does not matter much.
-
-
In the same file, the static initializer is called across classes in a top-down order even if there is an inheritance relationship between the classes.
- This means that there is no guarantee that all static member variables of the superclass must be initialized before the current class is initialized.
class Foo <: Bar { static let y: Int64 static init() { // called first y = x // error: not yet initialized variable } } open class Bar { static let x: Int64 static init() { // called second x = 2 } }
-
Static member variables must be initialized in only one way, either directly through the expression on the right or in the static initializer.
-
Although a variable static variable can be initialized by assigning a value directly and in the static initializer at the same time, the variable initialization in this case is only a direct assignment, and the assignment in the static initializer is considered as a simple re-assignment. This means that a value is directly assigned to the variable before the static initializer is called.
-
If an immutable static variable is assigned a value directly and in the static initializer at the same time, the compiler reports an error about re-assignment.
- This error is also reported if multiple values are assigned to an immutable static variable in the static initializer.
-
If a static variable is neither initialized directly nor in the static initializer, the compiler reports an error about the uninitialized variable.
-
The above can be detected by a special initialization analysis, depending on the implementation.
-
-
The static initializer cannot have any parameters.
-
The
return
expression cannot be used in the static initializer. -
Throwing an exception in the static initializer causes the program to terminate, just like throwing an exception in the expression on the right of a static variable.
-
Instance member variables or uninitialized static member variables not cannot be used in the static initializer.
-
The code of the static initializer is synchronous to prevent leakage of partially initialized classes or structures.
-
Static properties, including
getter
andsetter
, must be declared completely. -
Unlike static functions, static initializers cannot be used in extensions (within
extend
). -
Because the static initializer is called automatically and cannot be called explicitly, visibility modifiers (that is,
public
andprivate
) cannot be used to modify the static initializer.
The following is an example of the initialization analysis rules:
class Foo {
static let a: Int64
static var c: Int64
static var d: Int64 // error: uninitialized variable
static var e: Int64 = 2
static let f: Int64 // error: uninitialized variable
static let g: Int64 = 1
let x = c
static init() {
a = 1
b = 2
Foo.c // error: not yet initialized variable
let anotherFoo = Foo()
anotherFoo.x // error: not yet initialized variable
c = 3
e = 4
g = 2 // error: reassignment
}
static let b: Int64
}
Member Variables
Declaring Member Variables
Variables declared in classes and interfaces are called member variables. A member variable can be declared immutable by using the keyword let
or mutable by using the keyword var
.
Instance member variables outside the primary constructor can be declared with or without initial values. If there is an initial value, the initial value expression can use this variable to declare the previous member variable. These member variables are initialized before the constructor of the superclass is called. Therefore, the qualified name with super
cannot be used to access the member variables of the superclass in the initial value expression.
The following is a code example of variables:
open class A {
var m1: Int32 = 1
}
class C <: A {
var a: Int32 = 10
let b: Int32 = super.m1 // Error
}
Variable Modifiers
Variables in a class can be modified by access modifiers. For details, see Access Modifiers in [Packages and Modules].
In addition, if a variable in a class is modified by static
, it is a static variable of the class. static
can be used together with other access modifiers. A static variable is inherited by a subclass. The static variable of the subclass is the same as that of the superclass.
class C {
static var a = 1
}
var r1 = C.a // ok
Class Member Functions
Declaring and Defining Class Member Functions
You can define functions in a class and declare functions in an abstract class. The difference between definition and declaration is whether the function has a function body.
Class member functions are classified into instance member functions and static member functions.
The syntax for defining or declaring a class member function is as follows:
functionDefinition : modifiers 'func' identifier typeParameters? functionParameters (':' returnType)? genericConstraints? (('=' expression) | block)? ;
Instance Member Functions
The first implicit parameter of an instance member function is this
. Each time an instance member function is called, a complete object needs to be transferred first. Therefore, do not call an instance member function before the object is created, but the type of the function will not include the implicit parameter. The criterion for determining whether a class object is created is that the constructor of the class has been called.
Instance member functions can be classified into abstract member functions and non-abstract member functions.
Abstract Member Functions
An abstract member function can be declared only in an abstract class or interface. It does not have a function body.
abstract class A {
public func foo(): Unit // abstract member function
}
Non-abstract Member Functions
A non-abstract member function can be defined in any class and must have a function body.
class Test {
func foo(): Unit { // non-abstract member function
return
}
}
By default, abstract instance member functions have the semantics of open
. When defining an abstract instance member function in an abstract class, the open
modifier is optional, but public
or protected
must be explicitly specified as its visibility modifier.
Static Member Functions
A static member function is modified by the static
keyword. It does not belong to an instance but belongs to its type. In addition, a static function must have a function body.
Static member functions cannot use instance member variables, call instance member functions, or call the keyword
super
orthis
.A static member function can reference other static member functions or static member variables.
Static member functions can be modified by
private
,protected
,public
, orinternal
. For details, see [Access Modifiers].When a static member function is inherited by another subclass, the static member function is not copied to the original subclass.
Static member functions in both abstract and non-abstract classes must have implementations.
Example:
class C<T> {
static let a: Int32 = 0
static func foo(b: T): Int32 {
return a
}
}
main(): Int64 {
print("${C<Int32>.foo(3)}")
print("${C<Bool>.foo(true)}")
return 0
}
This program has its own static member variable a
and static function foo
for C<Int32>
and C<Bool>
respectively.
Static functions in a class can declare new type variables, which can also have constraints. When calling a type variable, you only need to provide a valid type for a class and then a valid type for a static function.
class C<T> {
static func foo<U>(a: U, b: T): U { a }
}
var a: Bool = C<Int32>.foo<Bool>(true, 1)
var b: String = C<Bool>.foo<String>("hello", false)
func f<V>(a: V): V { C<Int32>.foo<V>(a, 0) }
Modifiers of Class Member Functions
Class member functions can be modified by all access modifiers. For details, see [Access Modifiers].
Other modifiable non-access modifiers are as follows:
-
open
: If a member function needs to be overridden, use theopen
modifier. It conflicts with thestatic
modifier. When an instance member withopen
is inherited by a class,open
is also inherited. If a class has members modified byopen
but the current class is not modified byopen
or does not contain theopen
semantics,open
is not effective for these members. In this case, the compiler reports a warning. (No warning is reported for inheritedopen
members oroverride
members.)A function modified by
open
must be modified bypublic
orprotected
.// case 1 open class C1 { // In this case, the 'open' modifier before 'class C1' is required. public open func f() {} } class C2 <: C1 { public override func f() {} } // case 2 open class A { public open func f() {} } open class B <: A {} class C <: B { public override func f() {} // ok } // case 3 interface I { func f() {} } open class Base <: I {} // The function f Inherits the open modifier. class Sub <: Base { public override func f() {} // ok }
-
override
: When a function overrides another function that can be overridden,override
can be used to modify the function. (override
does not have the semantics ofopen
. If a function modified byoverride
needs to be overridden, you need to useopen
to modify the function.) The example is provided above. For details about function coverage rules, see chapter [Overriding]. -
static
: The function modified bystatic
is a static member function and must have a function body. Static member functions cannot be modified byopen
.Instance members of the class to which a static member function belongs cannot be accessed within the function. Static members of the class to which an instance member function belongs can be accessed within the function.
class C { static func f() {} // Cannot be overwritten and must have a function body. }
-
redef
: When a static function redefines a static function inherited from the supertype,redef
is an optional modifier for the static function.open class C1 { static func f1() {} static func f2() {} } class C2 <: C1 { redef static func f1() {} redef static func f2() {} }
Class Finalizer
A class finalizer is an instance member function of a class. It is called when an instance of the class is collected as garbage.
class C {
// below is a finalizer
~init() {}
}
The finalizer syntax is as follows:
classFinalizer
: '~' 'init' '(' ')' block
;
- A finalizer has no parameters, return types, generic type parameters, or modifiers, and cannot be called by users.
- Classes with finalizers cannot be modified by
open
. Only non-open
classes can have finalizers. - Only one finalizer can be defined for a class.
- Finalizers cannot be defined in extensions.
- The time when a finalizer is triggered is uncertain.
- A finalizer may be executed on any thread.
- The execution sequence of multiple finalizers is uncertain.
- The behavior of a finalizer throwing an uncaught exception is determined by the implementation.
- The behavior of creating threads or using thread synchronization in a finalizer is determined by the implementation.
- After a finalizer is executed, if the object can still be accessed, the consequences are determined by the implementation.
- Do not use
this
to escape from a finalizer. - Instance members cannot be called in a finalizer.
For example:
class SomeType0 {
~init() {} // OK
}
class SomeType1 {
~init(x: Int64) {} // Error, finalizer cannot have parameters
}
class SomeType2 {
private ~init() {} // Error, finalizer cannot have accessibility modifiers
}
class SomeType3 {
open ~init() {} // Error, finalizer cannot have open modifier
}
open class SomeType4 {
~init() {} // Error, open class can't have a finalizer
}
var GlobalVar: SomeType5 = SomeType5()
class SomeType5 {
~init() {
GlobalVar = this // do not escape `this` out of finalizer, otherwise, unexpected behavior may happen.
}
}
Class Member Properties
You can also define member properties in a class. For details about the syntax for defining member properties, see chapter [Properties].
Instantiating Classes
After defining a non-abstract class
type, you can create a class
instance. There are two methods of creating a class
instance based on whether the instance contains type variables:
- Create an instance of the non-general type
class
:ClassName
.ClassName
indicates the name of theclass
type, andarguments
indicates the argument list. TheClassName(arguments)
function calls the corresponding constructor based on the calling rule of overloaded functions (see [Function Overloading]), and then generates an instance of theClassName
function. For example:
class C {
var a: Int32 = 1
init(a: Int32) {
this.a = a
}
init(a: Int32, b: Int32) {
this.a = a + b
}
}
main() : Int64 {
var myC = C(2) // invoke the first constructor
var myC2 = C(3, 4) // invoke the second constructor
return 0
}
2.Create an instance of the generic class
: ClassName<Type1, Type2, ..., TypeK>(arguments)
. The only difference between creating an instance of a generic class
and creating an instance of a non-generic class
is whether generic parameters need to be instantiated. Generic arguments can be explicitly specified or omitted (in this case, the compiler infers the specific type based on the program context). For example:
class C<T, U> {
var a: T
var b: U
init(a: T, b: U) {
this.a = a
this.b = b
}
}
main() : Int64 {
var myC = C<Int32, Int64>(3, 4)
var myC2 = C(3,4) // The type of myC2 is inferred to C<Int64, Int64>.
return 0
}
Object Class
The Object
class is the superclass of all class
types (excluding the interface
type). The Object
class does not contain any member. That is, the Object
class is an empty class. There are parameterless constructors modified by public
in the Object
class.
This Type
In a class, the This
type placeholder is supported. It can be used only as the return type of the instance member function. During compilation, it is replaced with the type of the class to which the function belongs for type check.
- If the return type of a function is
This
, the function can return only the expression of theThis
type. - The expression of the
This
type containsthis
and other functions that returnThis
. - The
This
type is the subtype of the current type. TheThis
type can be automatically cast to the current type, but the latter cannot be cast to the former. - The
This
type cannot be explicitly used in the function body. If aThis
type expression is not used in return values, the current type is inferred. - If an instance member function only has the
This
expression without declaring the return type, its return type is inferred asThis
. - When an open function that contains
This
is overridden, the return type must remainThis
. - If the open function in the superclass returns the type of the superclass, the subclass can use
This
as the return type when it is overridden.
open class C1 {
func f(): This { // its type is `() -> C1`
return this
}
func f2() { // its type is `() -> C1`
return this
}
public open func f3(): C1 {
return this
}
}
class C2 <: C1 {
// member function f is inherited from C1, and its type is `() -> C2` now
public override func f3(): This { // ok
return this
}
}
var obj1: C2 = C2()
var obj2: C1 = C2()
var x = obj1.f() // During compilation, the type of x is C2
var y = obj2.f() // During compilation, the type of y is C1
Interfaces
An interface is used to define an abstract type. It does not contain data but can define the behavior of the type. If a type declares that it implements an interface and all members of the interface, it is considered that the interface is implemented.
The members of an interface can include instance member functions, static member functions, operator overloading functions, instance member properties, and static member properties. These members are abstract, but the default implementations can be defined for functions and properties.
Defining Interfaces
Syntax for Defining Interfaces
An interface is defined using the keyword interface in the following sequence: modifier (default or not), interface keyword, interface name, optional type parameter, whether to specify a superinterface, optional generic constraint, and interface body.
The following are some interface definitions:
interface I1 {}
public interface I2<T> {}
public interface I3<U> {}
public interface I4<V> <: I2<V> & I3<Int32> {}
The syntax for interfaces is defined as follows:
interfaceDefinition
: interfaceModifierList? 'interface' identifier
typeParameters?
('<:' superInterfaces)?
genericConstraints?
interfaceBody
;
The interface can be defined only at the top level.
The {}
of the interface body cannot be omitted.
interface I {} // {} not allowed to be omitted
Modifiers of an Interface
In this section, we will introduce the modifiers that can be used to define interfaces.
Access Modifiers
-
By default, if the access modifier is not used, the access can be performed only inside a package. You can also use the
public
modifier so that the package can be accessed externally.public interface I {} // can be accessed outside the package interface I2 {} // can only be accessed inside the package
Inheritance Modifiers
- By default, if the inheritance modifier is not used, an interface has the semantics modified by
open
. The interface can be inherited, implemented, or extended at any position where the interface can be accessed. Certainly, theopen
modifier may also be used explicitly. In addition, you can use thesealed
modifier to indicate that the interface can be inherited, implemented, or extended only in the package where the interface is defined. sealed
contains the semantics ofpublic
. Therefore,public
is optional when the sealed interface is defined.- Interfaces that inherit or classes that implement the
sealed
interface can still be modified bysealed
optionally. If the subinterface of thesealed
interface is modified bypublic
and not bysealed
, the subinterface can be inherited, implemented, or extended outside the package. - The type that inherits and implements the sealed interface may not be modified by
public
.
// package A
package A
public sealed interface I1 {} // OK
sealed interface I2 {} // OK, 'public' is optional when 'sealed' is used
open interface I3 {} // OK
interface I4 {} // OK, 'open' is optional
class C1 <: I1 {} // OK
public open class C2 <: I1 {} // OK
public sealed class C3 <: I1 {} // OK
extend Int64 <: I1 {} // OK
// package B
package B
import A.*
class S1 <: C2 {} // OK
class S2 <: C3 {} // Error, C3 is sealed class, cannot be inherited here.
Interface Members
An interface has the following members:
-
Members declared in the interface body (that is, in
{}
): static member functions, instance member functions, operator overloading functions, static member properties, and instance member properties. -
Members inherited from other interfaces. In the following example, the members of
I2
include the members that can be inherited fromI1
:interface I1 { func f(): Unit } interface I2 <: I1 {}
The BNF of interface members is as follows:
interfaceMemberDeclaration
: (functionDefinition|macroExpression|propertyDefinition) end*
;
Functions in an Interface
Declaring and Defining Functions in an Interface
An interface can contain instance member functions and static member functions. These functions are compiled in the same way as ordinary instance member functions and static member functions, but may not be implemented. These functions are called abstract functions.
An abstract function with an implementation is called a function with a default implementation.
The following is an example of an abstract function:
interface MyInterface {
func f1(): Unit // Default implementation not included
static func f2(): Unit // Default implementation not included
func f3(): Unit { // Default implementation included
return
}
static func f4(): Unit { // Default implementation included
return
}
}
An abstract function can have named parameters without default values.
interface MyInterface {
func f1(a!: Int64): Unit // OK
func f2(a!: Int64 = 1): Unit // Error, cannot have parameter default values
}
Modifiers of Functions and Properties in an Interface
If a function or property defined in an interface contains the public
semantics and is modified by public
, a compilation alarm is generated. Do not use the protected
, internal
, or private
modifier to modify the functions or properties defined in an interface.
The following is an incorrect example:
interface MyInterface {
public func f1(): Unit // Access modifiers cannot be used
private static func f2(): Unit // Access modifiers cannot be used
}
A function modified by static
is called a static member function, which can have no function body. The static
function without a function body cannot be directly called using an interface type. The static
function with a function body can be called using an interface type. When the type name of an interface is used to directly call its static member function, if the function directly or indirectly calls other static functions that are not implemented in the interface (or other interfaces), an error is reported during compilation.
interface I {
static func f1(): Unit
static func f2(): Unit {}
static func f3(): Unit {
f1()
}
}
main() {
I.f1() // Error, cannot directly call
I.f2() // OK
I.f3() // Error, f1 not implemented
}
By default, the instance member functions in the interface have the semantics of open
. The open
modifier is optional in defining instance member functions in an interface.
interface I {
open func foo1() {} // ok
func foo2() {} // ok
}
A function modified by mut
is a special instance member function, which can be used to abstract the variable behavior of the struct type.
interface I {
mut func f(): Unit
}
Member Properties in an Interface
You can also define member properties in an interface. For details about the syntax for defining member properties, see [Properties].
Default Implementations of an Interface
Abstract functions and abstract properties in an interface can have default implementations.
When an interface is inherited or implemented by another interface or type, if it is not re-implemented, its default implementations are copied to its subtype.
- In the default implementation of an instance member function,
this
can be used. The type ofthis
is that of the current interface. - Default implementations, like non-abstract member functions, can access all accessible elements in the current scope.
- Default implementations are a type of syntax sugar that provides a default behavior for the implementation type. An interface can use the default implementations of its superinterface.
- The default implementation does not belong to the inheritance semantics. Therefore, override, redef, or
super
cannot be used. - If the subtype of an inherited interface is a class, the
open
semantics is retained by default and can be overridden by the subclasses of the class.
interface I {
func f() { // f: () -> I
let a = this // a: I
return this
}
}
Interface Inheritance
An interface can inherit one or more interfaces.
interface I1<T> {}
interface I2 {}
interface I3<U> <: I1<U> {} // inherit a generic interface.
interface I4<V> <: I1<Int32> & I2 {} // inherit multiple interfaces.
When a subinterface inherits its superinterface, it inherits all members of the superinterface.
A non-generic interface cannot be directly inherited for multiple times, and a generic interface cannot be directly inherited for multiple times using the same type of parameters. For example:
interface I1 {}
interface I2 <: I1 & I1 {} // error
interface I3<T> {}
interface I4 <: I3<Int32> & I3<Int32> {} // error
interface I5<T> <: I3<T> & I3<Int32> {} // ok
If the type parameter of the generic interface I3
is set to Int32
, the compiler reports an error at the position where the type is used.
interface I1<T> {}
interface I2<T> <: I1<T> & I1<Int32> {} // ok
interface I3 <: I2<Int32> {} // error
main() {
var a: I2<Int32> // error
}
Default Implementations of a Subinterface
If an interface inherits a function or property without a default implementation from its superinterface, only the declaration of the function or property can be written in the interface (the default implementation can also be defined). In addition, the override
or redef
modifier before the function declaration or definition is optional. Example:
interface I1 {
func f1(): Unit
static func f2(): Unit
}
interface I2 <: I1 {
func f1(): Unit // ok
static func f2(): Unit // ok
}
interface I3 <: I1 {
override func f1(): Unit // ok
redef static func f2(): Unit // ok
}
interface I4 <: I1 {
func f1(): Unit {} // ok
static func f2(): Unit {} // ok
}
interface I5 <: I1 {
override func f1(): Unit {} // ok
redef static func f2(): Unit {} // ok
}
If an interface inherits a function or property with a default implementation from its superinterface, it is not allowed to write only the declaration of the function or property in the interface without implementation. If a new default implementation is provided in the interface, the override
or redef
modifier before the definition is optional. Example:
interface I1 {
func f1(): Unit {}
static func f2(): Unit {}
}
interface I2 <: I1 {
func f1(): Unit // error, 'f1' must has a new implementation
static func f2(): Unit // error, 'f2' must has a new implementation
}
interface I3 <: I1 {
override func f1(): Unit {} // ok
redef static func f2(): Unit {} // ok
}
interface I4 <: I1 {
func f1(): Unit {} // ok
static func f2(): Unit {} // ok
}
If an interface inherits the default implementations with the same signature member from its superinterfaces, the interface must provide new default implementations of its own version. Otherwise, a compilation error is reported.
interface I1 {
func f() {}
}
interface I2 {
func f() {}
}
interface I3 <: I1 & I2 {} // error, I3 must implement f: () -> Unit
Interface Implementation
Overriding and Overloading During Interface Implementation
When a type implements one or more interfaces, the rules are as follows:
- When a type that is not of the abstract class implements an interface, it must implement all functions and properties.
- When an abstract class implements an interface, it is allowed not to implement the functions and properties in the interface.
- The name and parameter list of the implementation function must be the same as those of the corresponding function in the interface.
- The return type of the implementation function must be the same as or a subtype of the corresponding function in the interface.
- For a generic function in an interface, the type variable constraint of the function must be looser or the same as that of the corresponding function in the interface.
- The
mut
modifier of the implementation property must be the same as the corresponding property in the interface. - The type of the implementation property must be the same as the corresponding property in the interface.
- If multiple interfaces have only one default implementation of the same function or property, it is allowed not to implement the function or property but use the default implementation.
- If multiple interfaces contain multiple default implementations of the same function or property, the function or property must be implemented. The default implementation cannot be used.
- If the same function or property in the interface already has the implementation type (inherited from the superclass or defined by the current class), the default implementation in any interface will not be used.
- When a type implements an interface, the
override
modifier (orredef
modifier) before the definition of a function or property is optional, regardless of whether the function or property in the interface has a default implementation.
In the following example: when a type that is not of the abstract class implements an interface, the abstract functions and abstract properties in the interface must be implemented, like f1 and f3
; the default implementation of a function in the interface can be omitted, like f2
; the abstract class is allowed not to implement the instance member functions in the interface, like the abstract class C1
that does not implement f1
in the interface I
.
interface I {
func f1(): Unit
func f2(): Unit {
return
}
static func f3(): Int64
}
class C <: I {
public func f1(): Unit {}
public func f2(): Unit {
return
}
public static func f3(): Int64 {
return 0
}
}
abstract class C1 <: I {
public static func f3(): Int64 {
return 1
}
}
Example: f
and g
in the I
interface are generic functions and the E
and F
classes meet the requirement that the type variable constraints of the implementation functions are looser than or same as those of the implemented functions, so the compilation is successful. Class D
does not meet the requirement, so an error is reported for the compilation.
// C <: B <: A
interface I {
static func f<T>(a: T): Unit where T <: B
static func g<T>(): Unit where T <: B
}
class D <: I {
public static func f<T>(a: T) where T <: C {} // Error, stricter constraint
public static func g<T>() where T <: C {} // Error, stricter constraint
}
class E <: I {
public static func f<T>(a: T) where T <: A {} // OK, looser constraint
public static func g<T>() where T <: A {} // OK, looser constraint
}
class F <: I {
public static func f<T>(a: T) where T <: B {} // OK, same constraint
public static func g<T>() where T <: B {} // OK, same constraint
}
More examples:
// case 1
interface I1 {
func f(): Unit
}
interface I2 {
func f(): Unit
}
class A <: I1 & I2 {
public func f(): Unit {} // ok
}
// case 2
interface I1 {
func f(): Unit
}
interface I2 {
func f(): Unit {}
}
open class A {
public open func f(): Unit {} // ok
}
class B <: A & I1 & I2 {
public override func f(): Unit {} // ok
}
// case 3
interface I1 {
func f(): Unit
}
interface I2 {
func f(): Unit {}
}
class A <: I1 & I2 {} // ok, f from I2
// case 4
interface I1 {
func f(): Unit {}
}
interface I2 {
func f(): Unit {}
}
class A <: I1 & I2 {} // error
class B <: I1 & I2 { // ok,
public func f(): Unit {}
}
// case 5
interface I1 {
func f(a: Int): Unit {}
}
interface I2 {
func f(a: Int): Unit {}
}
open class A {
public open func f(a: Int): Unit {}
}
open class B <: A & I1 & I2{ // ok, f from A
}
Rules for Overloading Functions During Interface Implementation
The functions in the parent and child scope must use the same names but different parameter lists.
The following examples show some cases of function overloading when a type implements an interface. Note that when a type implements an interface, implementation needs to be provided for the overloaded function declaration.
Example 1: In I1
and I2
, the function f
is declared with different parameter lists respectively. During class C
implementation, f
s whose parameter type is Unit
and Int32
are implemented at the same time.
interface I1 {
func f(): Unit
}
interface I2 {
func f(a: Int32): Unit
}
class C <: I1 & I2 {
public func f(): Unit {} // The f in I1 needs to be implemented.
public func f(a: Int32): Unit {} // The f in I2 needs to be implemented.
}
Example 2: In I1
and I2
, the default function f
is defined with different parameter lists respectively. f
does not need to be implemented in C
.
interface I1 {
func f() {}
}
interface I2 {
func f(a: Int32) {}
}
class C <: I1 & I2 {
}
Example 3: In I1
, a function whose name is f
and parameter type is Unit
is declared. In I2
, a function whose name is f
and parameter type is Int32
is defined. In class C
, the f
function whose parameter type is Unit
must be implemented.
interface I1 {
func f(): Unit
}
interface I2 {
func f(a: Int32):Unit {
return
}
}
class C <: I1 & I2 {
public func f(): Unit {
return
}
}
Any Interface
The Any
interface is an empty interface built in the language. By default, all interface types inherit the Any
interface, and all non-interface types implement the Any
interface. Therefore, all types can be used as subtypes of the Any
type.
class A {}
struct B {}
enum C { D }
main() {
var i: Any = A() // ok
i = B() // ok
i = C.D // ok
i = (1, 2) // ok
i = { => 123 } // ok
return 0
}
The Any interface can be explicitly declared in the type definition. If the Any interface is not explicitly declared, it will be implicitly implemented by the compiler. However, the Any interface cannot be re-implemented through extension.
class A <: Any {} // ok
class B {} // Implicit implement Any
extend B <: Any {} // error
Overriding, Overloading, Hiding, and Redefinition
Overriding
Definition of Overriding
When you define a non-abstract instance function for a type with the same name and open
semantics as that of its supertype, the optional override
can be used as the modifier to override the function of the supertype. Comply with the following rules when overriding functions:
-
The name of the function must be the same as that of the function to be overridden.
-
The parameter list of the function must be the same as that of the function to be overridden.
The same parameter list means that the functions have the same parameter types and number of parameters.
-
The return type of the function is the same as or its subtype of that of the function to be overridden.
-
If functions in multiple parent scopes are overridden by the same function, the overriding rule for each function is determined by the other rules above.
Example:
-
If the function
f
in theI
interface has the same parameter list and return type for theC1
andC2
classes, the overriding requirements are met. -
If
f1
has the same parameter list for theC1
andC2
classes, the return type inC1
isFather
, and the return type inC2
isChild
, the overriding requirements are met becauseChild
is a subtype ofFather
.
open class Father {}
class Child <: Father {}
open class C1 {
public open func f() {}
public open func f1(): Father { Father() }
}
interface I {
func f() {}
}
class C2 <: C1 & I {
public override func f() {} // OK.
public override func f1(): Child { Child() } // OK.
}
Some of the details to note include:
Functions modified by
private
in a class are not inherited and cannot be accessed in subclasses.Static functions cannot override instance functions.
Calling Overriding Functions
If a function of a type overrides a function of its supertype and the function is called in the program, the compiler selects the version of the function to be executed based on the type to which the runtime object points.
In the following example, the f
function is defined in the C1
class. The subclass C2
of C1
overrides f
. In addition, the a
and b
variables of the C1
type are defined, the object myC1
of C1
is assigned to a
, and the object myC2
of C2
is assigned to b
. When a
and b
are used to call f
, the corresponding function is selected based on the actual type at runtime.
open class C1 {
public open func f(): Unit {
return
}
}
class C2 <: C1 {
public override func f(): Unit {
return
}
}
var myC1 = C1()
var myC2 = C2()
// Assign the object of the superclass C1 to the variable of the C1 type.
var a: C1 = myC1
// Assign the object of the superclass C2 to the variable of the C1 type.
var b: C1 = myC2
// Invokes f of C1 based on the object type of at runtime
var c = a.f()
// Invokes f of C2 based on the object type of at runtime
var d = b.f()
Overloading
For details about the definition and calling of function overloading, see chapter [Function Overloading].
Overloading is not allowed between static member functions and instance member functions of classes or interfaces. If a static member function and an instance member function of a class or interface (such as a member function defined by the class or interface and inherited from the superclass or superinterface) have the same name, a compilation error is reported.
open class Base {}
class Sub <: Base {}
open class C{
static func foo(a: Base) {
}
}
open class B <: C {
func foo(a: Sub) { // Error
C.foo(Sub())
}
}
class A <: B {
// Static and instance functions cannot be overloaded.
static func foo(a: Sub) {} // Error
}
During overloading resolution, the functions defined in a type and its subtype are treated in the same scope priority.
Hiding
Members of a type cannot hide members of its supertype. Otherwise, a compilation error is reported.
In the following example, both the C1
and C2
classes have the instance variable x
with the same name. As a result, an error is reported during compilation.
open class C1 {
let x = 1
}
class C2 <: C1 {
let x = 2 // error
}
Redefinition
Redefining a Function
When you define a non-abstract static function for a type with the same name as that of its supertype, the optional redef
can be used as the modifier to redefine the function of the supertype. Comply with the following rules when redefining functions:
-
The name of the function must be the same as that of the function to be redefined.
-
The parameter list of the function must be the same as that of the function to be redefined.
The same parameter list means that the functions have the same parameter types and number of parameters.
-
The return type of the function is the same as or its subtype of that of the function to be redefined.
-
If the function to be redefined is a generic function, the type variable constraints of the redefining function must be looser or the same as those of the function to be redefined.
-
If functions in multiple parent scopes are redefined by the same function, the redefinition rule for each function is determined by the other rules above.
Example:
-
If the
f
function has the same parameter list and return types for theC1
andC2
classes, the redefinition requirements are met. -
If
f1
has the same parameter list for theC1
andC2
classes, and the return type inC1
isFather
, and the return type inC2
isChild
, the redefinition requirements are met becauseChild
is a subtype ofFather
.
open class Father {}
class Child <: Father {}
open class C1 {
public static func f() {}
public static func f1(): Father { Father() }
}
interface I {
static func f() {}
}
class C2 <: C1 & I {
public redef static func f() {} // OK.
public redef static func f1(): Child { Child() } // OK.
}
Example: If the f
and g
functions in the Base
class are generic functions, and the subclasses E
and F
meet the requirements that a redefining function has looser or the same type variable constraints as the function to be redefined, the compilation is successful. If the subclass D
does not meet the requirements, an error is reported during the compilation.
// C <: B <: A
open class Base {
static func f<T>(a: T): T where T <: B {...}
static func g<T>(): T where T <: B {...}
}
class D <: Base {
redef static func f<T>(a: T): T where T <: C {...} // Error, stricter constraint
redef static func g<T>(): T where T <: C {...} // Error, stricter constraint
}
class E <: Base {
redef static func f<T>(a: T): T where T <: A {...} // OK, looser constraint
redef static func g<T>(): T where T <: A {...} // OK, looser constraint
}
class F <: Base {
redef static func f<T>(a: T): T where T <: B {...} // OK, same constraint
redef static func g<T>(): T where T <: B {...} // OK, same constraint
}
The redef
modifier cannot be used for a static initializer (because static initializers cannot be explicitly called). Otherwise, the compiler reports an error.
Calling a Redefined Function
If a function of a type redefines that of its supertype and the function is called in a program, the compiler selects which version of the function to execute based on the type.
In the following example, the f
function defined in the C1
class is redefined in its subclass C2
. When C1
and C2
are used to call f
, the corresponding function is selected based on their types during compilation.
open class C1 {
static func f(): Unit {
return
}
}
class C2 <: C1 {
redef static func f(): Unit {
return
}
}
// Invokes f of C1
var c = C1.f()
// Invokes f of C2
var d = C2.f()
Access Control Level Restriction
Access modifiers within types are ranked at different levels based on their access scopes as follows:
public > protected > default > private
In this context, the behavior across access levels is specified as follows:
-
When a class inherits another class, if an instance member function of the subclass overrides that of the superclass, or a static member function of the subclass redefines that of the superclass, the access level of the subclass member function cannot be lower than that of the superclass member function.
-
When a type implements an interface, if a member function of the type implements an abstract function in the interface, the access level of the member function of the type cannot be lower than that of the abstract function.
The following is a code example of access level restriction:
open class A {
protected open func f() {}
}
interface I {
func m() {} // public by default
}
class C <: A & I {
private override func f() {} // Error: the access control of override function is lower than the overriden function
protected func m() {} // Error: the access control of function which implements abstract function is lower than the abstract function
}
Access Modifiers of a Non-top-level Member
The semantics of different access modifiers for non-top-level members are as follows:
private
: visible only to the current type or extended definition.internal
: visible only in the current package and its subclasses (including nested subclasses).protected
: visible in the current module and the subclasses of the current class.public
: visible in and out of the module.
Type/Extend | Package & Sub-Packages | Module & Sub-Classes | All Packages | |
---|---|---|---|---|
private | Y | N | N | N |
internal | Y | Y | N | N |
protected | Y | Y | Y | N |
public | Y | Y | Y | Y |
The access modifier of a type member can be different from that of the type itself. The default modifier for a type member except interface is internal
. (Default modifiers specify the modifier semantics when modifiers are omitted. These default modifiers can also be explicitly written.) The member functions and properties in interfaces cannot write access modifiers. Their access level is public
.
Restrictions on the Use of Generics in Classes and Interfaces
Duplicate Function Signatures Due to Instantiation Types
When you define a generic class, the following definitions of the C1
class and member functions are valid because functions can be overloaded.
open class C1<T> {
public func c1(a: Int32) {} // ok
public func c1(a: T) {} // ok
}
interface I1<T> {
func i1(a: Int32): Unit // ok
func i1(a: T): Unit // ok
}
var a = C1<Int32>() // error
class C2 <: C1<Int32> {...} // error
var b: I1<Int32> // error
class C3 <: I1<Int32> {...} // error
However, when the C1<T>
class needs to be instantiated as the C1<Int32>
class, the signatures of the two functions are the same. In this case, an error is reported where the C1<Int32>
type is used. Similarly, when the I1<Int32>
interface needs to be instantiated, the two function signatures declared in the interface are duplicate. In this case, an error is reported.
Generic Member Functions of Classes and Interfaces
In Cangjie, generic parameters cannot be declared for non-static abstract functions and functions modified by the open keyword in classes.
interface Bar {
func bar<T>(a: T): Unit // error
}
abstract class Foo {
func foo<T>(a: T): Unit // error
public open func goo<T>(a: T) { } // error
}
class Boo {
public open func Boo<T>(a: T) { } // error
}
For more information, see [Generics].