基本类型

类和对象

类和继承

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;它除了equalshasCode()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中,属性都有默认的getset方法,并且可以被重写,如上面的例子。如果子类中覆盖了父类中的属性,那么在父类中重写的被覆盖属性的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#中调用静态方法一样。

封闭类或密封类

用来限制类的继承,可以限制给定的某个对象只可能是某些指定的类型之一。有点像枚举。枚举类的值是有限的,每个枚举值常数都代表枚举类的一个实例,而密封类允许的子类类型是有限的,但每个子类的实例数并未做限制。
如何声明密封类?

  1. 使用sealed修饰符放在class关键字之前;
  2. 子类声明必须嵌套在密封类声明部分之内;

如:

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()
}

是否还有其他更有用的用途呢?

推荐阅读更多精彩内容