欢迎关注 二师兄Kotlin
转载请注明出处 二师兄kotlin
对象
Kotlin中对象使用 class
关键字来进行声明:
class Invoice {
}
类的声明由三部分组成:类名、类头(具体化的类型参数、主构造函数等)以及类体,被一堆花括号包围{}
( curly braces)。类头和类体均是可选的。如果类没有类体,花括号也可以被删掉。
class Empty
构造函数(Constructors)
Kotlin中的类可以包含 一个主构造函数和多个次构造函数。而主构造函数是类头的一部分:紧随在类名之后(类型参数可选)。
class Person constructor(firstName: String) {
}
主构造函数不能包含任何代码。初始化代码必须放置在 初始化块(initializer blocks
)中,通过关键字init
作为前缀。
在初始化一个实例的过程中,初始化块按照出现在类体中的顺序依次执行,可以和属性初始化进行穿插。
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
注意主构造函数的参数可以在初始化块中使用。同样的也可以被用来进行属性的初始化声明。
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
实际上,为了在主构造函数中声明属性并且初始化他们,Kotlin提供了一种简便的语法:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
和正常属性非常一样的方法,在首要构造器中的属性可以使可变的 (var)或者只读的 (val)。
如果构造器有注解或者可见性修饰符,关键字constructor
就是必须的,并且修饰符需要在该关键字之前:
class Customer public @Inject constructor(name: String) { ... }
想了解更多细节,可以看Visibility Modifiers.
次构造函数
类也可以声明次要构造器,次要构造器以constructor
作为前缀:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有首要构造器,每个次要构造器都需要去代理首要构造器,直接代理或间接通过另一个次要构造器去代理。代理同类的另一个构造器,需要使用关键字this
:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
注意:在初始化代码块的代码高效地成为首要构造器的一部分。首要构造器的代理会作为次要构造器的第一行语句执行,所以在所有初始化代码块的代码都在次要构造器之前执行。甚至,如果类没有首要构造器,代理都会隐式地发生,初始化代码块依旧会执行:
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
如果,一个非抽象类没有声明任何构造器(包括首要和次要),类仍然会生成一个没有参数的首要构造器。该构造器的可见性是public、如果你不想你的类有一个public构造器,你需要声明一个空的首要构造器,并使用非默认可见性修饰符:
class DontCreateMe private constructor () {
}
NOTE:注意: 在JVM中,如果首要构造器的所有参数都有默认数值,编译器将会生成一个额外的无参数的构造器,该构造器会使用默认数值。这样使得更容易在Kotlin中使用库,例如Jackson或者JPA(通过无参数构造器创建类的实例)
class Customer(val customerName: String = "")
创建类的实例
为了创建类的实例,我们将如调用普通函数一样调用构造器:
val invoice = Invoice()
val customer = Customer("Joe Smith")
- 注意Kotlin中不使用关键字
new
。
嵌套类、内部类、匿名内部类的创建在嵌套类中Nested classes
进行介绍。
类的成员
类可以包含:
- 构造函数和初始化块
- 函数
- 属性
- 嵌套类和内部类
- 对象声明
继承
在Kotlin中所有类都有一个共同的超类Any, 该超类是默认的,不需要超类的声明:
class Example // 隐式继承自Any
Any不是java.lang.Object
;特别地,它除了equals(), hashCode() and toString()没有其他任何成员。请参考Java interoperability
(Java互通性)章节。
为了声明一个显式的超类,我们将类型放置在类头后的冒号后面:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果类(子类)没有首要构造器,每个次要构造器必须使用关键字super初始化基本类型(超类),或者代理给另一个完成该任务的构造器。注意,在这种情况下,不同的次要构造器可以调用基本类型(超类)的不同的构造器:
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
open
注解在类中是与java中的final
相反的内容:open允许其他类继承自该类。默认的,在Kotlin中所有的类都是final(重点),这对应于Effective Java
, Item 17: Design and document for inheritance or else prohibit it.
重写方法
正如我们之前提到的,我们坚持在Kotlin中让事情都是显式的。不像Java,Kotlin对于覆盖的成员(这些成员需要有open修饰符)需要显式的注释:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
override
注解需要修饰Derived.v()
. 如果该注解遗漏,编译器就会报错。如果一个函数没有open
注解,类似Base.nv()
, 在子类中声明相同签名的方法就是非法的,无论是否有overide
。在一个final类中(即,一个没有open
注解的类), 禁止拥有open的成员(open修饰符无效).
被overide
标记的成员其本身就是open的,即,该成员可以在子类被重写。如果你想禁止“再重写”(重写父类的方法继续被子类重写),可以使用final
关键字:
open class AnotherDerived() : Base() {
final override fun v() {}
}
属性重写
与方法重写类似,在父类中已经声明的属性,如果要在一个派生类(derived class)中再次声明, 则必须使用override
打头,而且他们必须类型可以兼容。一个拥有初始化或者getter方法的属性都可以去重写旧属性。
open class Foo {
open val x: Int get() { ... }
}
class Bar1 : Foo() {
override val x: Int = ...
}
你可以用一个var
属性去重写一个val
属性,但是,但是反过来则不行(vice versa)。这样被允许的原因是一个val
属性本质上是声明了一个getter方法,所以用var
来重写相当于在派生类中给他添加了一个setter方法。
注意你可以使用override
关键字作为主构造函数属性声明的一部分。
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
调用父类实现
派生类中的代码通过super
关键字调用父类的方法和属性访问器实现:
open class Foo {
open fun f() { println("Foo.f()") }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f()
println("Bar.f()")
}
override val x: Int get() = super.x + 1
}
在一个内部类当中,访问外部类的超类需要使用 标记了外部类类名的super
关键字: super@Outer
:
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // Calls Foo's implementation of f()
println(super@Bar.x) // Uses Foo's implementation of x's getter
}
}
}
重写规则
在Kotlin中,继承的实现被这样一条规则所限制:如果一个类继承了他的多个超类中的同一个函数的多个实现,那么他必须重写这个函数且提供自己的实现(比如,使用所继承实现中的某一个实现)。为了表示你到底是用了哪个超类中哪个实现,我们可以使用 尖括号(angle brackets)包围的超类类名来标记super
。比如,super<Base>
:
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // interface members are 'open' by default
fun b() { print("b") }
}
class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}
继承A
和B
没有问题,而且对于在继承类C
中对于函数a()
和b()
的实现也没有问题。但是对于函数f()
,对于C
来说我们有两个实现,所以我们必须去重写f()
并且提供自己的实现来消除歧义(eliminates the ambiguity)。
抽象类
类和他的一些成员可以声明成abstract
。一个抽象成员不可以包含实现。注意我们不需要用open
来标注一个抽象类或者他的函数,默认就是open
(it goes without saying 不用说)。
我们可用一个抽象的成员来重写一个非抽象、但开放的成员。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
友元对象(Companion Objects)
在Kotlin中,不想java或者C#,类没有静态方法。大多数情况下,更推荐使用 包级方法(package-level functions )来替代。
如果你需要写这样一个函数,他可以访问一个类的内部但并非通过类对象实例(比如,一个工厂方法),你可以把它写成一个object declaration成员放在类中。
更具体地说(Even more specifically ),如果你在类中声明了一个
companion object,你将可以实现像调用Java/C#中静态方法那样的调用语法来调用companion object
的成员,只需要类名作为标识。