类和对象
类和继承
类
Kotlin中使用关键字class
声明类
class Person{
}
类的声明由类名、类头(指定类的类型参数、主构造函数等)和类体(后面花括号包围的部分);如果一个类没有类体,可以省略花括号。
class Person
构造函数
在Kotlin中一个类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。
class Person constructor(firstName: String){
}
如果主构造函数没有任何的注解和可见性修饰符,可以省略constructor
关键字。
class Person(firstName: String){
}
主构造函数不包含任何代码。初始化代码可以放在以init
关键字作为前缀的初始化块(initializer block)中:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
注意主构造函数的参数可以在初始化块中使用。也可以在类体内声明属性时,在属性的初始化器中使用:
class Customer(name: String){
init{
logger.info("Customer initialized with value ${name}") //在初始化块中使用主构造函数的参数
}
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){
//do some thing
}
次构造函数
类也可是声明前缀有constructor
的次构造函数:
class Person{
constructor(parent: Person){
parent.chilren.add(this)
}
}
如果类有主构造函数,每个次构造函数都需要委托给主构造函数,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用this
关键字即可:
class Person(val name: String){
constructor(name: String,parent: Person) : this(name){
parent.children.add(this)
}
}
如果一个类非抽象并且没有声明任何(主或次)构造函数,它会默认生成一个无参的主构造函数。并且这个默认无参构造函数的可见性为public
。如果你不希望你的类有一个公有的构造函数,你需要声明一个带飞默认可见性的空主构造函数:
class DontCreatMe private constructor(){
}
在JVM上,如果主构造函数的所有参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值,这使得Kotlin更易于使用像Jackson或JPA这样通过无参构造函数创建类的实例的库。
class Customer(val customerName: String = "")
创建类的实例
要创建类的实例,我们只需要像调用普通函数一样调用构造函数:
val user = User()
val customer = Customer("Jon Smith")
可以发现Kotlin中创建类的实例,是不需要
new
关键字的,Kotlin中也没有这个关键字
类成员
- 构造函数和初始化块
- 函数
- 属性
- 嵌套类和内部类
- 对象声明
继承
在Kotlin中所有类都有一个共同的超类Any
,所有类都直接或间接派生自这个类,类似Java
中的Object
class Example (){//默认派生自Any
}
但是,Any
并不是java.lang.Object
;它除了equals
、hasCode()
、toString()
外没有其它成员。
要继承一个类,我们把超类放到派生类的类头的冒号后面:
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)
}
在Kotlin中,open
标注与Java
中的final
相反,它标注的类才允许被其他类继承。默认情况下,在Kotlin中所有的类都是final
的,这正对应了Effective Java
中的要么为继承而设计,并提供文档说明,要么禁止继承。
覆盖方法
在Kotlin中力求清晰显式。与Java不同,Kotlin中需要显式标注可以被覆盖的成员(或称开放的成员)和覆盖后的成员:
open class Base {
open fun v(){} //可以被复写的成员用open标注
fun nv(){}
}
class Derived() : Base(){
final override fun v(){} //复写后的成员用override标注,默认override标注的成员也是开放的,如果想再次限制其被覆盖可以显式的用final标注
}
覆盖属性
属性覆盖和方法覆盖类似;在子类中声明超类中的同名属性就是覆盖超类中的属性,并且必须以override
开头作为标注,并且它们的类型必须兼容。每个声明的属性可以由具有初始化器的属性或者具有getter
方法的属性覆盖。
open class Foo{
open val x: Int?= null
get(){//重写get方法
field = 100
return field
}
}
class Bar1 : Foo(){
override val x: Int? = null
}
在Kotlin中,属性都有默认的get
、set
方法,并且可以被重写,如上面的例子。如果子类中覆盖了父类中的属性,那么在父类中重写的被覆盖属性的get
方法,在子类中是无效的。
val可以被var覆盖
但是var不能被val覆盖
覆盖规则
如果一个类从它的直接超类继承相同成员的多个实现,那么这个类必须必须覆盖这个成员并提供自己的实现(或者用继承来的其中之一)。为了表示采用从哪个超类中继承的实现,我们使用super<Base>
表示:
open class A {
open fun f() {
print("A")
}
fun a() {
print("a")
}
}
interface B {
fun f() {
print("B")
} // 接口成员默认就是“open”的,并且可以有实现
fun b() {
print("b")
}
}
class C() : A(), B {
// 编译器要求覆盖 f():
override fun f() {
super<A>.f() // 调用 A.f()
super<B>.f() // 调用 B.f()
}
}
类C
同时继承了两个不同f()
实现,所以必须在C
中覆盖f()
并且提供自己的实现来消除歧义。
抽象类
类和其中某些成员可以声明为abstract
的。抽象成员在本类中可以不实现。抽象类默认是开放的,不需要用open
关键字标注。并且我们可以用一个抽象成员覆盖一个非抽象的开放成员
open class Base{
open fun f(){}
}
abstract class Derived : Base(){
override abstract fun f()
}
伴生对象
在Kotlin中没有静态方法。在大多数情况下,它建议采用包级函数(在类外面与包在一个级别)。
如果你需要写一个无需类的实例来调用,但需要访问类内部的的函数,你可以把它写成该类内对象声明中的一员,而不是类本身的成员。
这里说的类内声明的对象,就是伴生对象,伴生对象内声明的成员只需要类名作为限定符就可以访问,语法与Java/C#中调用静态方法一样。
封闭类或密封类
用来限制类的继承,可以限制给定的某个对象只可能是某些指定的类型之一。有点像枚举。枚举类的值是有限的,每个枚举值常数都代表枚举类的一个实例,而密封类允许的子类类型是有限的,但每个子类的实例数并未做限制。
如何声明密封类?
- 使用
sealed
修饰符放在class
关键字之前; - 子类声明必须嵌套在密封类声明部分之内;
如:
sealed class Expr {
class Const(val number:Double):Expr()
class Sum(val e1: Expr,val e2: Expr):Expr()
object NotAnumber : Expr()//这里声明的是个单利类
}
用途:
在when
表达式中可以验证分支语句覆盖了所有条件,避免使用else
分支处理例外情况。
sealed class Expr {
class Const(val number:Double):Expr()
class Sum(val e1: Expr,val e2: Expr):Expr()
object NotAnumber : Expr()
}
是否还有其他更有用的用途呢?