类和接口
类
类的定义
在仓颉编程语言中,通过关键字 class
定义一个类,类定义包含以下几个部分:
- 可选的修饰符
class
关键字- 类名,必须是合法的 identifier
- 可选的类型参数
- 可选的指定父类或者父接口(写在
<:
后面,使用&
分隔),如果存在父类,父类必须写在第一个,否则编译报错; - 可选的泛型约束
- 类体
类只能定义在源文件顶层,类定义的语法如下:
classDefinition
: classModifierList? 'class' identifier
typeParameters?
('<:' superClassOrInterfaces)?
genericConstraints?
classBody
;
superClassOrInterfaces
: classType ('&' superInterfaces)?
| superInterfaces
;
类定义的例子如下:
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 {}
类的修饰符
在此小节中,我们将介绍定义类可以使用的修饰符。
访问修饰符
类可以被所有访问修饰符修饰,默认的可访问性为 internal
。详细内容请参考包和模块管理章节访问修饰符。
继承性修饰符
-
open
:类用open
修饰,表示允许其他类从这个类继承。/* The following class is declared with modifier `open`, indicating that it can be inherited. */ open class C1 { func foo(): Unit { return } } class C2 <: C1 {}
需要注意的是:
没有用
open
修饰的非抽象类不能被任何类继承。
-
sealed
:修饰符表示只能在 class 定义所在的包内继承该 class。-
sealed
已经蕴含了public
的语义,所以定义 sealed class 时的public
修饰符是可选的。 -
sealed
的子类可以不是sealed
类,仍可被open
/sealed
修饰,或不使用任何继承性修饰符。若sealed
类的子类同时被public
和open
修饰,则其子类可在包外被继承; -
sealed
的子类可以不被public
修饰。
// 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.
-
需要注意的是:
没有用
open
或sealed
修饰的非抽象类不能被任何类继承。
抽象类修饰符
-
abstract
:该类属于抽象类,与普通类不同的是,在抽象类中除了可以定义普通的函数,还允许声明抽象函数,只有用该修饰符修饰的类才为抽象类。如果一个函数没有函数体,我们称其为抽象函数。需要注意的是:
- 抽象类
abstract
修饰符已经包含了可被继承的语义,因此抽象类定义时的open
修饰符是可选的,也可以使用sealed
修饰符修饰抽象类,表示该抽象类只能在本包被继承; - 抽象类中禁止定义
private
的抽象函数; - 不能为抽象类创建实例;
- 允许抽象类的抽象子类不实现父类中的抽象函数;
- 抽象类的非抽象子类必须实现父类中的所有抽象函数。
- 抽象类
类继承
类只支持单继承。类通过<: superClass
的方式来指定当前类的直接父类(父类是某个已经定义了的类)。
以下示例展示了类C2
继承类C1
的语法:
open class C1 {}
class C2 <: C1 {}
父类可以是一个泛型类,只需要在继承时提供合法的类型即可。例如:非泛型类C2
与泛型类C3
继承泛型类C1
:
open class C1<T> {}
class C2 <: C1<Int32> {}
class C3<U> <: C1<U> {}
所有类都有一个父类,对于没有定义父类的类,默认其父类为Object
。Object
例外,没有父类。
class Empty {} // Inherit from Object implicitly: class Empty <: Object {}
类仅支持单继承。以下示例中的语法将引起编译错误。
open class C1 {}
open class C2 {}
class C3 <: C1 & C2 {} // Error: Multiple inheritance is not supported.
当一个类继承另一个类时,将被继承的类称为父类,将产生继承行为的类称为子类。
open class C1 {} // C1 is superclass
class C2 <: C1 {} // C2 is subclass
子类将继承父类的所有成员,私有成员和构造函数除外。
子类可以直接访问父类的成员,但是在覆盖时,将不能直接通过名字来访问父类被覆盖的实例成员,这时可以通过 super
来指定(super
指向的是当前类对象的直接父类)或创建对象并通过对象来访问。
实现接口
类支持实现一个或多个接口,通过 <: I1 & I2 & ... & Ik
的方式声明当前类想要实现的接口,多个接口之间用 &
分隔。如果当前类也指定了父类,则接口需要出现在父类后面。例如:
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 {}
接口也可以是泛型的,此时在实现泛型接口时需要给出合法的类型参数。例如:
interface I1<T> {}
interface I2<U> {}
class C1 <: I1<Int32> & I2<Bool> {}
class C2<K, V> <: I1<V> & I2<K> {}
当类型实现接口时,对于非泛型接口不能直接实现多次,对于泛型接口不能用同样的类型参数直接实现多次。例如:
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
如果上述定义的泛型类C3
在使用时被应用了类型参数Int32
,导致重复实现了两个相同类型的接口,那么编译器会在类型被使用到的位置报错:
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
关于接口的详细描述在章节[接口]。
类体
类体表示当前类包括的内容,类体由大括号包围,包含以下内容:
- 可选的静态初始化器
- 可选的主构造函数
- 可选的构造函数
- 可选的成员变量定义
- 可选的成员函数、成员操作符函数定义或声明
- 可选的成员属性定义或声明
- 可选的宏调用表达式
- 可选的类终结器
类体的语法定义如下:
classBody
: '{'
classMemberDeclaration*
classPrimaryInit?
classMemberDeclaration*
'}'
;
classMemberDeclaration
: classInit
| staticInit
| variableDeclaration
| functionDefinition
| operatorFunctionDefinition
| macroExpression
| propertyDefinition
| classFinalizer
;
上述类体的语法定义中,包含以下内容:
staticInit
代表一个静态初始化器的定义,一个类最多只能定义一个静态初始化器;
classPrimaryInit
指的是主构造函数的定义,一个类最多只能定义一个;
classInit
表示 init
构造函数的定义;
variableDeclaration
表示成员变量的声明;
operatorFunctionDefinition
表示操作符重载成员函数的定义;
macroExpression
表示宏调用表达式,宏展开后依然要符合 classMemberDeclaration
的语法定义;
propertyDefinition
表示属性的定义;
classFinalizer
表示类的终结器的定义;
类体中引入的定义或声明均属于类的成员,将在[类的成员]章节中详细介绍。
类的成员
类的成员包括:
- 从父类(若存在)继承而来的成员。
- 如果类实现了接口,其成员还包括从接口中继承而来的成员。
- 在类体中声明或定义的成员,包括:静态初始化器、主构造函数、
init
构造函数、静态成员变量、实例成员变量、静态成员函数、实例成员函数、静态成员属性、实例成员属性。
类的成员可以从不同的维度进行分类:
从是否被static
修饰可以分为静态成员和实例成员。其中静态成员指不需要实例化类对象就能访问的成员,实例成员指必须先实例化类对象才能通过对象访问到的成员。
从成员的种类区分有静态初始化器、构造函数、成员函数、成员变量、成员属性。
需要注意的是:
- 所有的静态成员都不能通过对象名访问
构造函数
在仓颉编程语言中,有两种构造函数:主构造函数和 init
构造函数 (简称构造函数)。
主构造函数
主构造函数的语法定义如下:
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'
;
主构造函数的定义包括以下几个部分:
**1、修饰符:**可选。主构造函数可以使用 public
、 protected
、 private
其中之一修饰,都不使用是包内可见;详见访问修饰符
**2、主构造函数名:**与类型名一致。主构造函数名前不允许使用func
关键字。
**3、形参列表:**主构造函数与 init
构造函数不同的是,前者有两种形参:普通形参和成员变量形参。普通形参的语法和语义与函数定义中的形参一致。
引入成员变量形参是为了减少代码冗余。成员变量形参的定义,同时包含形参和成员变量的定义,除此之外还表示了通过形参给成员变量赋值的语义。省略的定义和表达式会由编译器自动生成。
-
成员变量形参的语法和成员变量定义语法一致,此外,和普通形参一样支持使用
!
来标注是否为命名形参; -
成员变量形参的修饰符有:
public
,protected
,private
;详见访问修饰符 -
成员变量形参只允许实例成员变量,即不允许使用
static
修饰; -
成员变量形参不能与主构造函数外的成员变量同名;
-
成员变量形参可以没有初始值。这是因为主构造函数会由编译器生成一个对应的构造函数,将在构造函数体内完成将形参给成员变量的赋值;
-
成员变量形参也可以有初始值,初始值仅用于构造函数的参数默认值。成员变量形参的初值表达式中可以引用该成员变量定义之前已经定义的其他形参或成员变量(不包括定义在主构造函数外的实例成员变量),但不能修改这些形参和成员变量的值。需要注意的是,成员变量形参的初始值只在主构造函数中有效,不会在成员变量定义中包含初始值;
-
成员变量形参后不允许出现普通形参,并且要遵循函数定义时的参数顺序,命名形参后不允许出现非命名形参。
**4、主构造函数体:**如果显式调用父类构造函数,函数体内第一个表达式必须是调用父类构造函数的表达式;同时,主构造函数中不允许使用 this
调用本类中其它构造函数。父类构造函数调用之后,主构造函数体内允许写表达式、局部变量声明、局部函数定义,其中声明、定义和表达式需要满足init
构造函数中对this
和super
使用的规则。具体规则详见[init 构造函数]。
主构造函数定义的例子如下:
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
) {
}
}
主构造函数定义时,成员变量形参后不允许出现普通形参的例子如下:
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
) {
}
}
主构造函数是 init
构造函数的语法糖,编译器会自动生成与主构造函数对应的构造函数和成员变量的定义。自动生成的构造函数形式如下:
- 其修饰符与主构造函数修饰符一致;
- 其形参从左到右的顺序与主构造函数形参列表中声明的形参一致;
- 构造函数体内形式依次如下:
- 依次是对成员变量的赋值,语法形式为
this.x = x
, 其中x
为成员变量名; - 主构造函数体中的代码;
- 依次是对成员变量的赋值,语法形式为
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
}
*/
}
一个类最多可以定义一个主构造函数,除了主构造函数之外,可以照常定义其他构造函数,但要求其他构造函数必须和主构造函数所对应的构造函数构成重载。
init
构造函数
构造函数使用init
关键字指定,不能带有func
关键字,不能为构造函数显式定义返回类型,且必须有函数体。构造函数的返回类型为 Unit
类型。
构造函数的语法如下:
Init
: nonSMemberModifier? 'init' '(' InitParamLists? ')'
'{'
(superCallExression | initCallExpression)?
( expression
| variableDeclaration
| functionDefinition)*
'}'
;
InitParamLists
: unnamedParameterList (',' namedParameterList)?
| namedParameterList
;
可以在init
前添加访问修饰符来限制该构造函数的可访问范围:详见访问修饰符
当构造一个类的对象时,实际上会调用此类的构造函数,如果没有参数类型匹配且可访问的构造函数,则会编译报错。
在一个类中,用户可以为这个类提供多个init
构造函数,这些构造函数必须符合函数重载的要求。关于函数重载的详细描述,请参见[函数重载]。
class C {
init() {}
init(name: String, age: Int32) {}
}
创建类的实例时调用的构造函数,将根据以下顺序执行类中的表达式:
-
先初始化主构造函数之外定义的有缺省值的变量;
-
如果构造函数体内未显式调用父类构造函数或本类其它构造函数,则调用父类的无参构造函数
super()
,如果父类没有无参构造函数,则报错; -
执行构造函数体内的代码;
如果一个类既没有定义主构造函数,也没有定义 init
构造函数,则会尝试生成一个(public
修饰的)无参构造函数。如果父类没有无参构造函数或者存在本类的实例成员变量没有初始值,则编译报错。
构造函数、this
和 super
的使用规则:
- 禁止使用实例成员变量
this.variableName
及其语法糖variableName
和super.variableName
作为构造函数参数的默认值; init
构造函数可以调用父类构造函数或本类其它构造函数,但两者之间只能调用一个。如果调用,必须在构造函数体内的第一个表达式处,在此之前不能有任何表达式或声明;- 若构造函数没有显式调用其他构造函数,也没有显式调用父类构造函数,编译器会在该构造函数体的开始处插入直接父类的无参构造函数的调用。如果此时父类没有无参构造函数,则会编译报错;
- 构造函数体内调用完父类构造函数或本类其它构造函数之后允许使用
super.x
访问父类的实例成员变量x
; - 若构造函数没有显式调用其他构造函数,则需要确保 return 之前本类声明的所有实例成员变量均完成初始化,否则编译报错;
- 可以被继承的类的构造函数中禁止调用实例成员函数或实例成员属性。
- 可以被继承的类的构造函数中禁止
this
逃逸。 - 构造函数在所有实例成员变量完成初始化之前,禁止使用隐式传参或捕获了
this
的函数或lambda
,禁止使用super.f
访问父类的实例成员方法f
,禁止使用单独的this
表达式,但允许使用this.x
或其语法糖x
来访问已经完成初始化的成员变量x
; - 在构造函数体外,不允许通过
this
调用该类的构造函数; - 禁止构造函数之间循环依赖,否则将编译报错。
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() {}
}
静态初始化器
类或结构体中的静态变量也可以在静态初始化器中通过赋值表达式来初始化。不支持在枚举和接口中使用静态初始化器。
静态初始化器的语法如下:
staticInit
: 'static' 'init' '(' ')'
'{'
expressionOrDeclarations?
'}'
;
静态初始化器的规则如下:
- 静态初始化器会被自动调用,开发者不能显式调用;
- 静态初始化器会在它所属的包被加载时被调用,就像静态变量的初始化表达式;
- 一个类或结构中最多只能有一个静态初始化器;
- 对于一个非泛型的类或结构体,静态初始化器保证仅被调用一次;
- 对于一个泛型的类或结构体,静态初始化器在每个不同的类型实例化中,保证仅被调用一次;
- 注意,如果没有该泛型类或结构体的类型实例化,则静态初始化器根本不会被调用;
- 静态初始化器在这个类或结构中所有静态成员变量的直接初始化后被调用,就像构造函数是在所有实例字段的直接初始化后被调用一样;
- 这意味着可以在静态初始化器中引用进一步声明的静态成员变量;
- 这也意味着静态初始化器可以位于类或结构中的任何位置,顺序并不重要;
- 在同一个文件中,跨越多个类,即使这些类之间存在继承关系,静态初始化器仍然以自上而下的顺序被调用。
- 这意味着不能保证父类的所有静态成员变量必须在当前类的初始化之前被初始化。
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 } }
- 这意味着不能保证父类的所有静态成员变量必须在当前类的初始化之前被初始化。
- 静态成员变量必须只以一种方式初始化,要么直接通过右侧表达式,要么在静态初始化器中;
- 尽管一个可变的静态变量可以同时被直接赋值和在静态初始化器中被赋值来进行初始化,但在这种情况下的变量初始化只是直接赋值,而静态初始化器中的赋值被认为是简单的重新赋值。这意味着在静态初始化器被调用之前,该变量会有一个直接赋值;
- 如果一个不可变的静态变量同时被直接赋值和在静态初始化器中被赋值,编译器会报一个关于重新赋值的错误;
- 如果一个不可变的静态变量在静态初始化器中被多次赋值,也会报告这个错误。
- 如果一个静态变量既没有直接初始化也没有在静态初始化器中初始化,编译器会报一个关于未初始化变量的错误。
- 上述情况可由一个特殊的初始化分析检测出来的,它取决于实现方式
- 静态初始化器不能有任何参数。
- 静态初始化器中不允许使用
return
表达式。 - 在静态初始化器中抛出异常会导致程序的终止,就像在静态变量的右侧表达式中抛出异常一样。
- 实例成员变量,或者未初始化完成的静态成员变量不能在静态初始化器中使用。
- 静态初始化器的代码是同步的,以防止部分初始化的类或结构的泄漏。
- 静态属性仍应以完整的方式声明,包括
getter
和setter
; - 与静态函数不同,静态初始化器不能在扩展中(在
extend
内)使用。 - 由于静态初始化器是自动调用的,并且无法显式调用它,因此可见性修饰符(即
public
、private
)不能用于修饰静态初始化器。
下面用一个例子来演示初始化分析的规则:
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
}
成员变量
成员变量的声明
声明在类、接口中的变量称为成员变量。成员变量可以用关键字let
声明为不可变的,也可以用关键字var
声明为可变的。
主构造函数之外的实例成员变量声明时可以有初始值,也可以没有初始值。如果有初始值,初始值表达式可以使用此变量声明之前的成员变量。由于这些成员变量的初始化的执行顺序是在调用父类构造函数之前,初始值表达式中禁止使用带 super
的限定名访问父类的成员变量。
以下是变量的代码示例:
open class A {
var m1: Int32 = 1
}
class C <: A {
var a: Int32 = 10
let b: Int32 = super.m1 // Error
}
变量的修饰符
类中的变量可以被访问修饰符修饰,详细内容请参考包和模块管理章节访问修饰符
另外,如果类中的一个变量用 static
修饰,则它属于类的静态变量。static
可以与其他访问修饰符同时使用。静态变量会被子类继承,子类和父类的静态变量是同一个。
class C {
static var a = 1
}
var r1 = C.a // ok
类成员函数
类成员函数的声明和定义
在类中允许定义函数,同时允许在抽象类中声明函数。定义和声明的区别在于该函数是否有函数体。
类成员的函数分为实例成员函数、静态成员函数。
类成员函数定义或声明的语法如下:
functionDefinition : modifiers 'func' identifier typeParameters? functionParameters (':' returnType)? genericConstraints? (('=' expression) | block)? ;
实例成员函数
实例成员函数的第一个隐式参数是this
,每当调用实例成员函数时,都意味着需要先传入一个完整的对象,因此在对象未创建完成时就调用实例成员函数的行为是被禁止的,但是该函数的类型将不包括该隐式参数。类对象创建完成的判断依据是已经调用了类的构造函数。
实例成员函数可以分为抽象成员函数和非抽象成员函数。
抽象成员函数
抽象成员函数只能在抽象类或接口中声明,没有函数体。
abstract class A {
public func foo(): Unit // abstract member function
}
非抽象成员函数
非抽象成员函数允许在任何类中定义,必须有函数体。
class Test {
func foo(): Unit { // non-abstract member function
return
}
}
抽象实例成员函数默认具有 open
的语义。在抽象类中定义抽象实例成员函数时,open
修饰符是可选的,但必须显式指定它的可见性修饰符为 public
或 protected
。
静态成员函数
静态成员函数用static
关键字修饰,它不属于某个实例,而是属于它所在的类型。同时静态函数必须有函数体。
- 静态成员函数中不能使用实例成员变量,不能调用实例成员函数,不能调用
super
或this
关键字。- 静态成员函数中可以引用其他静态成员函数或静态成员变量。
- 静态成员函数可以用
private
、protected
、public
、internal
修饰。详见访问修饰符- 静态成员函数在被其它子类继承时,这个静态成员函数不会被拷贝到子类中。
- 抽象类和非抽象类中的静态成员函数都必须拥有实现。
例如:
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
}
这段程序对于C<Int32>
与C<Bool>
分别有他们各自的静态成员变量a
与静态函数foo
。
类中的静态函数可以声明新的类型变元,这些类型变元也可以存在约束。在调用时只需对类给出合法的类型,然后对静态函数给出合法的类型就可以了:
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) }
类成员函数的修饰符
类成员函数可以被所有访问修饰符修饰,详见访问修饰符
其他可修饰的非访问修饰符如下:
-
open
:一个成员函数想要被覆盖,需要用open
修饰符修饰。它与static
修饰符有冲突。当带open
修饰的实例成员被 class 继承时,该open
的修饰符也会被继承。如果 class 中存在被open
修饰的成员,而当前 class 没有被open
修饰或不包含open
语义,那么这些open
修饰的成员仍然没有open
效果,编译器对这种情况会报 warning 提示(对于继承下来的 open 成员或者 override 的成员不需要报 warning)。一个被
open
修饰的函数,必须被public
或protected
修饰。// 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
:当一个函数覆盖另一个可以被覆盖的函数时,允许可选地使用override
进行修饰(override
不具备open
的语义,如果用override
修饰的函数还需要允许能被覆盖,需要重新用open
修饰)。示例如上。函数覆盖的规则请参见[覆盖]章节。 -
static
:用static
修饰的函数为静态成员函数,必须有函数体。静态成员函数不能用open
修饰。静态成员函数内不可以访问所在类的实例成员;实例成员函数内能访问所在类的静态成员。
class C { static func f() {} // Cannot be overwritten and must have a function body. }
-
redef
:当一个静态函数重定义继承自父类型的静态函数时,允许可选地使用redef
进行修饰。open class C1 { static func f1() {} static func f2() {} } class C2 <: C1 { redef static func f1() {} redef static func f2() {} }
类终结器
类终结器是类的一个实例成员函数,这个方法在类的实例被垃圾回收的时候被调用。
class C {
// below is a finalizer
~init() {}
}
终结器的语法如下:
classFinalizer
: '~' 'init' '(' ')' block
;
- 终结器没有参数,没有返回类型,没有泛型类型参数,没有任何修饰符,也不可以被用户调用。
- 带有终结器的类不可被
open
修饰,只有非open
的类可以拥有终结器。 - 一个类最多只能定义一个终结器。
- 终结器不可以定义在扩展中。
- 终结器被触发的时机是不确定的。
- 终结器可能在任意一个线程上执行。
- 多个终结器的执行顺序是不确定的。
- 终结器向外抛出未捕获异常的行为由实现决定。
- 终结器中创建线程或者使用线程同步功能的行为由实现决定。
- 终结器执行结束之后,如果这个对象还可以被继续访问,后果由实现决定。
- 不允许
this
逃逸出终结器。 - 终结器中不允许调用实例成员方法。
举例如下:
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
类型之后,就可以创建对应的 class
实例。创建 class
实例的方式按照是否包含类型变元可分为两种:
- 创建非泛型
class
的实例:ClassName(arguments)
。其中ClassName
为class
类型的名字,arguments
为实参列表。ClassName(arguments)
会根据重载函数的调用规则(参见[函数重载])调用对应的构造函数,然后生成ClassName
的一个实例。举例如下:
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
}
- 创建泛型
class
的实例:ClassName<Type1, Type2, ... , TypeK>(arguments)
。与创建非泛型class
的实例的差别仅在于需要对泛型参数进行实例化,泛型实参可以显式指定,也可以省略(此时由编译器根据程序上下文推断出具体的类型)。举例如下:
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 类
Object
类是所有 class
类型的父类(不包括 interface
类型),Object
类中不包含任何成员,即 Object
是一个“空”的类。Object
有 public
修饰的无参构造函数。
This 类型
在类内部,我们支持 This
类型占位符,它只能被作为实例成员函数的返回类型来使用,并且在编译时会被替换为该函数所在类的类型,从而进行类型检查。
- 返回类型是
This
的函数,只能返回This
类型表达式,其它表达式都不允许。 This
类型的表达式包含this
和 调用其它返回This
的函数。This
类型是当前类型的子类型,This
可以自动 cast 成当前类型,但反之不行。- 函数体内不能显式使用
This
类型。在返回值以外的地方使用This
类型表达式都会被推断为当前类型。 - 如果实例成员函数没有声明返回类型,并且只存在返回
This
类型表达式时,当前函数的返回类型会推断为This
。 - 含
This
的 open 函数在 override 时,返回类型必须保持This
类型。 - 父类中的 open 函数返回类型如果是父类,子类在 override 时可以使用
This
作为返回类型。
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
接口
接口用来定义一个抽象类型,它不包含数据,但可以定义类型的行为。一个类型如果声明实现某接口,并且实现了该接口中所有的成员,就被称为实现了该接口。
接口的成员可以包含实例成员函数、静态成员函数、操作符重载函数、实例成员属性和静态成员属性,这些成员都是抽象的,但函数和属性可以定义默认实现。
接口定义
接口定义的语法
接口的定义使用interface
关键字,接口定义依次为:可缺省的修饰符、interface
关键字、接口名、可选的类型参数、是否指定父接口、可选的泛型约束、接口体的定义
以下示例是一些接口定义:
interface I1 {}
public interface I2<T> {}
public interface I3<U> {}
public interface I4<V> <: I2<V> & I3<Int32> {}
接口定义的语法如下:
interfaceDefinition
: interfaceModifierList? 'interface' identifier
typeParameters?
('<:' superInterfaces)?
genericConstraints?
interfaceBody
;
接口只可以定义在 top level。
接口体的{}
不允许省略
interface I {} // {} not allowed to be omitted
接口的修饰符
在此小节中,我们将介绍定义接口可以使用的修饰符。
访问修饰符
-
访问修饰符:默认,即不使用访问修饰符,表示只能在包内部访问。也可以使用
public
修饰符表示包外部可访问;public interface I {} // can be accessed outside the package interface I2 {} // can only be accessed inside the package
继承性修饰符
- 继承修饰符:默认,即不使用继承修饰符即可表示接口具有
open
修饰的语义,能在任何可以访问 interface 的位置继承、实现或扩展 interface,当然,也可以显式地使用open
修饰符修饰;也可以使用sealed
修饰符表示只能在 interface 定义所在的包内继承、实现或扩展该 interface。 sealed
已经蕴含了public
的语义,所以定义 sealed interface 时的public
修饰符是可选的。- 继承
sealed
接口的子接口或实现sealed
接口的类仍可被sealed
修饰或不使用sealed
修饰。若sealed
接口的子接口被public
修饰,且不被sealed
修饰,则其子接口可在包外被继承、实现或扩展; - 继承、实现 sealed 接口的类型可以不被
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.
接口成员
接口的成员包括:
-
在接口体中声明(即在
{}
中声明)的成员:静态成员函数、实例成员函数、操作符重载函数、静态成员属性和实例成员属性。 -
从其他接口继承而来的成员。如以下示例中,
I2
的成员将包括I1
中允许被继承的成员。interface I1 { func f(): Unit } interface I2 <: I1 {}
接口成员的 BNF 如下:
interfaceMemberDeclaration
: (functionDefinition|macroExpression|propertyDefinition) end*
;
接口中的函数
接口中函数的声明和定义
接口中可以包含实例成员函数和静态成员函数,这些函数与普通实例成员函数和静态成员函数的编写方式一样,但可以没有函数实现,这些函数被称为抽象函数。
对于拥有实现的抽象函数,我们称该函数拥有默认实现。
以下是包含抽象函数的示例:
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
}
}
抽象函数可以拥有命名参数,但不能拥有参数默认值。
interface MyInterface {
func f1(a!: Int64): Unit // OK
func f2(a!: Int64 = 1): Unit // Error, cannot have parameter default values
}
接口中函数和属性的修饰符
接口中定义的函数或属性已经蕴含 public
语义,使用 public
修饰会产生编译告警。不允许使用 protected
、 internal
和 private
修饰符修饰接口中定义的函数或属性。
以下是错误的示例:
interface MyInterface {
public func f1(): Unit // Access modifiers cannot be used
private static func f2(): Unit // Access modifiers cannot be used
}
使用 static
修饰的函数被称为静态成员函数,可以没有函数体。没有函数体的 static
函数不能直接使用接口类型调用,拥有函数体的 static
函数可以使用接口类型调用。当使用 interface 类型名直接调用其静态成员函数时,如果这个函数里面直接或间接调用了接口中(自己或其它接口)没有实现的其它静态函数,则编译报错。
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
}
接口中的实例成员函数默认具有 open
的语义。在接口中定义实例成员函数时,open
修饰符是可选的。
interface I {
open func foo1() {} // ok
func foo2() {} // ok
}
使用 mut
修饰的函数是一种特殊的实例成员函数,可以用于抽象 struct 类型的可变行为。
interface I {
mut func f(): Unit
}
接口中的成员属性
interface 中也可以定义成员属性,定义成员属性的语法参见 7 章。
接口的默认实现
interface 中的抽象函数和抽象属性都可以拥有默认实现。
interface 被其它 interface 或类型继承或实现的时候,如果该 interface 的默认实现没有被重新实现,则这些默认实现会被拷贝子类型中。
- 实例成员函数的默认实现中可以使用
this
,this
的类型是当前 interface。 - 默认实现和非抽象的成员函数一样,可以访问当前作用域中所有可访问的元素。
- 默认实现是一种语法糖,可以给实现类型提供默认行为,子接口可以沿用父接口的默认实现。
- 默认实现不属于继承语义,因此不能使用 override/redef,也不能使用
super
。 - 继承接口的子类型为类时,默认实现保留
open
语义,可以被类的子类重写。
interface I {
func f() { // f: () -> I
let a = this // a: I
return this
}
}
接口继承
接口允许继承一个或多个接口。
interface I1<T> {}
interface I2 {}
interface I3<U> <: I1<U> {} // inherit a generic interface.
interface I4<V> <: I1<Int32> & I2 {} // inherit multiple interfaces.
子接口继承父接口时,会继承父接口的所有成员:
子接口继承父接口时,对于非泛型接口不能直接继承多次,对于泛型接口不能使用相同类型参数直接继承多次。例如:
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
如果泛型接口I3
在使用时被给了类型参数为Int32
,那么编译器会在类型使用的位置报错:
interface I1<T> {}
interface I2<T> <: I1<T> & I1<Int32> {} // ok
interface I3 <: I2<Int32> {} // error
main() {
var a: I2<Int32> // error
}
子接口中的默认实现
子接口如果继承了父接口中没有默认实现的函数或属性,则在子接口中允许仅写此函数或属性的声明(当然也允许定义默认实现),并且函数声明或定义前的 override
或 redef
修饰符是可选的。示例如下:
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
}
子接口如果继承了父接口中有默认实现的函数或属性,则在子接口中不允许仅写此函数或属性的声明而没有实现,如果子接口中给出新的默认实现,那么定义前的 override
或 redef
修饰符是可选的。示例如下:
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
}
如果子接口继承的多个父接口中拥有相同签名成员的默认实现,子接口必须提供自己版本的新默认实现,否则会编译报错。
interface I1 {
func f() {}
}
interface I2 {
func f() {}
}
interface I3 <: I1 & I2 {} // error, I3 must implement f: () -> Unit
实现接口
实现接口时的覆盖与重载
一个类型实现一个或多个接口时规则如下:
- 抽象类以外的类型实现接口时,必须实现所有的函数、属性。
- 抽象类实现接口时,允许不实现接口中的函数和属性。
- 实现函数的函数名、参数列表必须与接口中对应函数的相同。
- 实现函数的返回类型应该与接口中对应函数的返回类型相同或者为其子类型。
- 如果接口中的函数为泛型函数,则要求实现函数的类型变元约束比接口中对应函数更宽松或相同。
- 实现属性的
mut
修饰符必须与接口中相应的属性相同。 - 实现属性的类型,必须与接口中对应的属性相同。
- 如果多个接口中只有同一函数或属性的一个默认实现,则实现类型可以不实现该函数或属性,使用默认实现。
- 如果多个接口中包含同一函数或属性的多个默认实现,则实现类型必须要实现该函数或属性,无法使用默认实现。
- 如果实现类型已经存在 (从父类继承或本类定义) 接口中同一函数或属性的实现,则不会再使用任何接口中的默认实现。
- 类型在实现接口时,函数或属性定义前的
override
修饰符(或redef
修饰符)是可选的,无论接口中的函数或属性是否存在默认实现。
抽象类以外的类型实现接口时,必须对接口中的抽象函数和抽象属性进行实现,如下示例中的 f1, f3
;允许不使用接口中的函数默认实现,如下示例中的 f2
;抽象类允许不实现接口中的实例成员函数,如下示例中抽象类 C1
并没有实现接口 I
中的 f1
。
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
}
}
示例:接口 I
中的函数 f
和 g
为泛型函数,class E
和 F
满足实现函数的类型变元约束比被实现函数的更宽松或相同的要求,编译成功;class D
不满足要求,则编译报错。
// 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
}
更多例子:
// 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
}
实现接口时函数重载的规则:
父作用域函数与子作用域函数的函数名必须相同,但参数列表必须不同。
以下几个例子中给出了类型实现接口时构成函数重载的一些情形,需要注意的是当类型现接口时需要为被重载的函数声明提供实现。
示例一:分别在I1
和I2
中声明了函数名相同、参数列表不同的函数f
。需要在实现类C
中同时实现参数类型为Unit
和参数类型为Int32
的f
。
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.
}
示例二:分别在I1
和I2
中定义了函数相同、参数列表不同的默认函数f
。不需要在C
中实现f
。
interface I1 {
func f() {}
}
interface I2 {
func f(a: Int32) {}
}
class C <: I1 & I2 {
}
示例三:在I1
中声明了函数名为f
,参数类型为Unit
的函数;在I2
中定义了函数名为f
,参数类型为Int32
的函数。类C
中必须实现参数类型为Unit
的函数f
。
interface I1 {
func f(): Unit
}
interface I2 {
func f(a: Int32):Unit {
return
}
}
class C <: I1 & I2 {
public func f(): Unit {
return
}
}
Any 接口
Any
接口是一个语言内置的空接口,所有 interface 类型都默认继承它,所有非 interface 类型都默认实现它,因此所有类型都可以作为 Any
类型的子类型使用。
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
}
在类型定义处可以显式声明实现 Any 接口,如果没有则会由编译器隐式实现,但不能使用扩展重新实现 Any 接口。
class A <: Any {} // ok
class B {} // Implicit implement Any
extend B <: Any {} // error
覆盖、重载、遮盖、重定义
覆盖
覆盖的定义
子类型中定义父类型中已经存在的同名非抽象、具有 open
语义的实例函数时,允许使用可选的 override
进行修饰以表示是对父类型中同名函数的覆盖。函数的覆盖需要遵循以下规则:
-
覆盖函数与被覆盖函数的函数名必须相同。
-
覆盖函数与被覆盖函数的参数列表必须相同。
参数列表相同指函数的参数个数、参数类型相同
-
覆盖函数的返回类型与被覆盖函数的返回类型相同或为其子类型。
-
同一个函数覆盖多个父作用域中出现的函数,每个函数的覆盖规则由上面的其他规则确定。
示例:
- 类
C1
、C2
,接口I
中的函数f
,参数参数列表相同,返回类型相同,满足覆盖的要求。 - 类
C1
与C2
中的函数f1
,参数列表相同,在C1
中的返回类型是Father
,在C2
中的返回类型是Child
,由于Child
是Father
的子类型,因此满足覆盖的规则。
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.
}
需要注意的一些细节包括:
- 类中用
private
修饰的函数不会被继承,无法在子类中访问。- 静态函数不能覆盖实例函数
覆盖函数的调用
如果子类型覆盖了父类型中的函数,并且在程序中调用了此函数,则编译器会根据运行时对象指向的类型来选择执行此函数的哪个版本。
以下示例中,在类C1
定义了函数f
,其子类C2
中覆盖了f
,同时定义了类型为C1
的变量a
与b
,并将C1
的对象myC1
赋给a
,将C2
的对象myC2
赋给b
。通过a
和b
来调用函数f
时,会在运行时根据它们实际的类型来选择对应的函数。
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()
重载
关于函数重载定义及调用的详细描述,请参见[函数重载]章节。
类和接口的静态成员函数和实例成员函数之间不允许重载。如果类或接口的静态成员函数和实例成员函数(包括本类/接口定义的和从父类或父接口继承过来的成员函数)同名,则报编译错误。
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
}
重载决议时,父类型和子类型中定义的函数当成同一作用域优先级处理。
遮盖
子类型的成员不可遮盖父类型的成员。如果发生遮盖,将编译报错。
如下示例中,类 C1
和 C2
都有同名实例变量 x
,编译报错。
open class C1 {
let x = 1
}
class C2 <: C1 {
let x = 2 // error
}
重定义
重定义函数的定义
子类型中定义父类型中已经存在的同名非抽象静态函数时,允许使用可选的 redef
进行修饰以表示是对父类型中同名函数的重定义。函数的重定义需要遵循以下规则:
-
函数与被重定义函数的函数名必须相同。
-
函数与被重定义函数的参数列表必须相同。
参数列表相同指函数的参数个数、参数类型相同。
-
函数的返回类型与被重定义函数的返回类型相同或为其子类型。
-
如果被重定义函数为泛型函数,则要求重定义函数的类型变元约束比被实现函数更宽松或相同。
-
同一个函数重定义多个父作用域中出现的函数,每个函数的覆盖规则由上面的其他规则确定。
示例:
- 类
C1
、C2
,接口I
中的函数f
,参数参数列表相同,返回类型相同,满足重定义的要求。 - 类
C1
与C2
中的函数f1
,参数列表相同,在C1
中的返回类型是Father
,在C2
中的返回类型是Child
,由于Child
是Father
的子类型,因此满足重定义的规则。
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.
}
示例:基类 Base
中的函数 f
和 g
为泛型函数,子类 E
和 F
满足重定义函数比被重定义函数的类型变元约束更宽松或相同的要求,编译成功;子类 D
不满足要求,则编译报错。
// 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
}
redef
修饰符不能用于修饰静态初始化器(因为静态初始化器不能被显式调用),否则编译器会报告错误。
重定义函数的调用
如果子类型重定义了父类型中的函数,并且在程序中调用了此函数,则编译器会根据类型来选择执行此函数的哪个版本。
以下示例中,在类 C1
定义了函数 f
,其子类 C2
中重定义了 f
。通过 C1
和 C2
来调用函数 f
时,会在编译时根据它们的类型来选择对应的函数。
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()
访问控制等级限制
根据访问修饰符所允许的可访问范围大小,规定访问修饰符等级如下:
- 类型内部访问修饰符等级为
public > protected > default > private
。
在此等级下,对跨访问等级的行为规定如下:
-
在子类继承父类时,子类的实例成员函数覆盖父类的实例成员函数,或者子类的静态成员函数重定义父类的静态成员函数,子类成员函数的可访问等级不得修改为小于父类成员函数的访问等级;
-
在类型实现接口时,类型的成员函数实现了接口中的抽象函数,类型的成员函数的可访问等级不得修改为小于抽象函数的访问等级。
以下是访问等级限制的代码示例:
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
}
非顶层成员的访问修饰符
在修饰非顶层成员时不同访问修饰符的语义如下:
private
表示仅当前类型或扩展定义内可见。internal
表示仅当前包及子包(包括子包的子包)内可见。protected
表示当前 module 及当前类的子类可见。public
表示 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 |
类型成员的访问修饰符可以不同于类型本身。除接口外类型成员的默认修饰符(默认修饰符是指在省略情况下的修饰符语义,这些默认修饰符也允许显式写出)是 internal
,接口中的成员函数和属性不可以写访问修饰符,它们的访问级别等同于 public
。
泛型在类与接口中使用的限制
实例化类型导致函数签名重复
在定义泛型类时,由于函数是可以重载的,所以下面的C1
类与成员函数的定义是合法的。
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
但是当类C1<T>
需要被实例化为C1<Int32>
时,会出现两个函数签名完全相同的情形,此时,在使用到C1<Int32>
类型位置报错。同理,当接口I1<Int32>
需要被实例化时,也会造成其部声明的两个函数签名重复,此时也会报错。
类与接口的泛型成员函数
在仓颉语言中,非静态抽象函数与类中被 open 关键字修饰的函数不能声明泛型参数。
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
}
更多可以见第 9 章泛型 7.2 小节。