访问规则
扩展的修饰符
扩展本身不能使用修饰符修饰。
例如,下面的例子中对 A 的直接扩展前使用了 public
修饰,将编译报错。
public class A {}
public extend A {} // Error, expected no modifier before extend
扩展成员可使用的修饰符有:static
、public
、protected
、internal
、private
、mut
。
- 使用
private
修饰的成员只能在本扩展内使用,外部不可见。 - 使用
internal
修饰的成员可以在当前包及子包(包括子包的子包)内使用,这是默认行为。 - 使用
protected
修饰的成员在本模块内可以被访问(受导出规则限制)。当被扩展类型是 class 时,该 class 的子类定义体内也能访问。 - 使用
static
修饰的成员,只能通过类型名访问,不能通过实例对象访问。 - 对
struct
类型的扩展可以定义mut
函数。
package p1
public open class A {}
extend A {
public func f1() {}
protected func f2() {}
private func f3() {}
static func f4() {}
}
main() {
A.f4()
var a = A()
a.f1()
a.f2()
}
扩展内的成员定义不支持使用 open
、override
、redef
修饰。
class Foo {
public open func f() {}
static func h() {}
}
extend Foo {
public override func f() {} // Error
public open func g() {} // Error
redef static func h() {} // Error
}
扩展的孤儿规则
为一个其它 package
的类型实现另一个 package
的接口,可能造成理解上的困扰。
为了防止一个类型被意外实现不合适的接口,仓颉不允许定义孤儿扩展,指的是既不与接口(包含接口继承链上的所有接口)定义在同一个包中,也不与被扩展类型定义在同一个包中的接口扩展。
如下代码所示,我们不能在 package c
中,为 package a
里的 Foo
实现 package b
里的 Bar
。
我们只能在 package a
或者在 package b
中为 Foo
实现 Bar
。
// package a
public class Foo {}
// package b
public interface Bar {}
// package c
import a.Foo
import b.Bar
extend Foo <: Bar {} // Error
扩展的访问和遮盖
扩展的实例成员与类型定义处一样可以使用 this
,this
的功能保持一致。同样也可以省略 this
访问成员。扩展的实例成员不能使用 super
。
class A {
var v = 0
}
extend A {
func f() {
print(this.v) // Ok
print(v) // Ok
}
}
扩展不能访问被扩展类型中 private
修饰的成员。
class A {
private var v1 = 0
protected var v2 = 0
}
extend A {
func f() {
print(v1) // Error
print(v2) // Ok
}
}
扩展不能遮盖被扩展类型的任何成员。
class A {
func f() {}
}
extend A {
func f() {} // Error
}
扩展也不允许遮盖其它扩展增加的任何成员。
class A {}
extend A {
func f() {}
}
extend A {
func f() {} // Error
}
在同一个包内,对同一类型可以扩展多次,并且在扩展中可以直接调用被扩展类型的其他扩展中非 private
修饰的函数。
class Foo {}
extend Foo { // OK
private func f() {}
func g() {}
}
extend Foo { // OK
func h() {
g() // OK
f() // Error
}
}
扩展泛型类型时,可以使用额外的泛型约束。泛型类型的任意两个扩展之间的可见性规则如下:
- 如果两个扩展的约束相同,则两个扩展相互可见,即两个扩展内可以直接使用对方内的函数或属性;
- 如果两个扩展的约束不同,且两个扩展的约束有包含关系,约束更宽松的扩展对约束更严格的扩展可见,反之,不可见;
- 当两个扩展的约束不同时,且两个约束不存在包含关系,则两个扩展均互相不可见。
示例:假设对同一个类型 E<X>
的两个扩展分别为扩展 1
和扩展 2
,X
的约束在扩展 1
中比扩展 2
中更严格,那么扩展 1
中的函数和属性对扩展 2
均不可见,反之,扩展 2
中的函数和属性对扩展 1
可见。
open class A {}
class B <: A {}
class E<X> {}
interface I1 {
func f1(): Unit
}
interface I2 {
func f2(): Unit
}
extend<X> E<X> <: I1 where X <: B { // extension 1
public func f1(): Unit {
f2() // OK
}
}
extend<X> E<X> <: I2 where X <: A { // extension 2
public func f2(): Unit {
f1() // Error
}
}
扩展的导入导出
扩展也是可以被导入和导出的,但是扩展本身不能使用 public
修饰,扩展的导出有一套特殊的规则。
对于直接扩展,只有当扩展与被扩展的类型在同一个包中,并且被扩展的类型和扩展中添加的成员都使用 public
或 protected
修饰时,扩展的功能才会被导出。
除此以外的直接扩展均不能被导出,只能在当前包使用。
如以下代码所示,Foo
是使用 public
修饰的类型,并且 f
与 Foo
在同一个包内,因此 f
会跟随 Foo
一起被导出。而 g
和 Foo
不在同一个包,因此 g
不会被导出。
// package a
public class Foo {}
extend Foo {
public func f() {}
}
// package b
import a.*
extend Foo {
public func g() {}
}
// package c
import a.*
import b.*
main() {
let a = Foo()
a.f() // OK
a.g() // Error
}
对于接口扩展则分为两种情况:
- 如果接口扩展和被扩展类型在同一个包,但接口是来自导入的,只有当被扩展类型使用
public
修饰时,扩展的功能才会被导出。 - 如果接口扩展与接口在同一个包,则只有当接口是使用
public
修饰时,扩展的功能才会被导出。
如下代码所示,Foo
和 I
都使用了 public
修饰,因此对 Foo
的扩展就可以被导出。
// package a
public class Foo {}
public interface I {
func g(): Unit
}
extend Foo <: I {
public func g(): Unit {}
}
// package b
import a.*
main() {
let a: I = Foo()
a.g()
}
与扩展的导出类似,扩展的导入也不需要显式地用 import
导入,扩展的导入只需要导入被扩展的类型和接口,就可以导入可访问的所有扩展。
如下面的代码所示,在 package b
中,只需要导入 Foo
就可以使用 Foo
对应的扩展中的函数 f
。
而对于接口扩展,需要同时导入被扩展的类型和扩展的接口才能使用,因此在 package c
中,需要同时导入 Foo
和 I
才能使用对应扩展中的函数 g
。
// package a
public class Foo {}
extend Foo {
public func f() {}
}
// package b
import a.Foo
public interface I {
func g(): Unit
}
extend Foo <: I {
public func g() {
this.f() // OK
}
}
// package c
import a.Foo
import b.I
func test() {
let a = Foo()
a.f() // OK
a.g() // OK
}