本文最后更新于:1 个月前
Keywords: constructor、实例初始化顺序、Any
、覆盖方法、覆盖属性、初始化顺序、继承、抽象类、伴生对象、lateinit
、幕后字段、幕后属性、可见性修饰符、扩展、sealed
、inline
、委托
类与继承
类
- 如果主构造函数没有任何注解后者可见性修饰符,可以省略 constructor 关键字。如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面。
- 在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起。
- 如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可。
- 初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块。
- 在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。
继承
Any
超类有三个方法:equals()
、hashCode()
与toString()
。- 默认情况下,Kotlin 类是最终(final)的:它们不能被继承。 要使一个类可继承,请用
open
关键字标记它。 - 如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化。
如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数。
覆盖方法
open
、override
- 标记为
override
的成员本身是开放的,它可以在子类中覆盖。如果想禁止再次覆盖,使用final
关键字:
open class Rectangle() : Shape() {
final override fun draw() { /*……*/ }
}
覆盖属性
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override
开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 get
方法的属性覆盖。
可以用 var
属性覆盖 val
属性,反之则不行。一个 val
属性本质上声明了一个 get
方法,将其覆盖为 var
只是在子类中额外声明一个 set
方法。
派生类初始化顺序
在构造派生类的新实例的过程中,第一步完成其基类的初始化。(在之前只有对基类构造函数参数的求值)。
设计一个基类时,应该避免在构造函数、属性初始化器以及 init
块中使用 open
成员。
覆盖规则
一个类从它的直接超类继承相同成员的多个实现,它必须覆盖这个成员并提供自己的实现。为了表示采用从哪个超类型继承的实现,使用由尖括号中超类型名限定的 super 如:super<Base>
。
伴生对象
如果在类内声明了一个伴生对象,就可以用类名为限定符访问其成员。
属性与字段
延迟初始化属性与变量
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检测。
为处理这种情况,你可以用 lateinit
修饰符标记该属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 var
属性,并且仅当该属性没有自定义 getter
或 setter
时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit
属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。
检测一个 lateinit var 是否已初始化(自1.2起)
if (foo::bar.isInitialized) {
println(foo.bar)
}
此检测仅对可词法级访问的属性可用,即声明位于同一个类型内、位于其中一个外围类型中或者位于相同文件的顶层的属性。
可见性修饰符
- Kotlin 中有四个可见性修饰符:
private
、protected
、internal
和public
。默认可见性为public
。 - 局部变量、函数和类不能有可见性修饰符。
扩展
扩展属性不能有初始化器。它们的行为只能由显示的提供 getters/setters 定义。
密封类
要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。(在 Kotlin 1.1 之前, 该规则更加严格:子类必须嵌套在密封类声明的内部)。在某种意义上,密封类是枚举类的扩展。
泛型
内联类
有时候,业务逻辑需要围绕某种类型创建包装器。然⽽,由于额外的堆内存分配问题,它会引⼊运⾏时的性能开销。此外,如果被包装的类型是原⽣类型,性能的损失是很糟糕的,因为原⽣类型通常在运⾏时就进⾏了⼤量优化,然⽽他们
的包装器却没有得到任何特殊的处理。
为了解决这类问题,Kotlin 引入了 内联类
,它通过在类的前面定义一个 inline
修饰符来声明。
内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例。
inline class Password(val value: String)
// 不存在 'Password' 类的真实实例对象
// 在运行时, 'securePassword' 仅仅包含 'String'
val securePassword = Passwrod("Don't try this in production")
成员
内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数:
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // `greet` ⽅法会作为⼀个静态⽅法被调⽤
println(name.length) // 属性的 get ⽅法会作为⼀个静态⽅法被调⽤
}
但内联类不能含有 init 代码块,不能含有幕后字段。
内联类的继承
内联类允许去继承接口。禁止内联类参与到类的继承关系结构中。这就意味着内联类不能继承其他的类而且必须是 final。
委托
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!