final类

Kotlin中没有添加修饰符直接以class开头的都是final类。

构造函数

在Kotlin中,一个类有一个primary constructor,一个或多个secondary constructors。primary constructor 属于类头部分,它在类名之后。

class Person constructor(firstName: String) {
}
//constructor关键字可以被省略
class Person(firstName: String) {
}

primary constructor不能包含任何的代码,初始代码可以放在初始块中,初始化代码块以init关键字开头。

class Customer(firstName: String, lastName: String, email: String) {
  var firstName: String
  var lastName: String
  var email: String

  init {
    this.firstName = firstName
    this.lastName = lastName
    this.email = email
  }
}

也可以直接把primary constructor中的参数直接声明成为类的属性,定义的方法是在参数名前加上 var 或者 val 关键字,val 是代表属性是常量。在创建类的时候,调用构造函数就直接把它们进行了初始化,这样就不用在类中单独声明类的属性了。

class Customer(
  var firstName: String,
  var lastName: String,
  var email: String)

如果希望类的构造器是私有的,就需要显示的指明。

class DontCreateMe private constructor () {
}

除了primary constructory,还有secondary constructors,同样也是constructor开头,位于类体中。 如果primary constructory和secondary constructors同时存在,每个secondary constructor需要委托到primary constructor中去,或者间接通过另一个secondary constructors来委托。使用方法就是使用this,表示委托到本类的另一个构造函数中。

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

open类

Kotlin中如果一个类需要能被继承,必须在类声明处使用open关键字,open关键字跟Java中的final是恰恰相反的。上面说过,Kotlin中一个类默认是public final的,final类自然是不能被继承。

在Kotlin中所以的类都有一个默认的父类Any,这个类似于Java中的Object。 Any不属于java.lang.Object,其只有三个方法:

public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
重写规则

如果一个类继承自多个类,并且它的这些父类都有同一个可以重写的函数,那么这个类必须重写这个函数并且提供它自己的实现,另外在子类中指示父类的方法是super,A为父类类名,这样就可以指示不同的父类了。

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都有f()函数可以重写,所以C类,必须重写这个函数f(),主要是为了消除歧义。

注意,在子类中被标记为override的成员函数本身也是可以被重新的,也就是open的,如果希望它禁止被重写,需要使用final关键字。

open class AnotherDerived() : Base() {
  final override fun v() {}
}

属性重写也使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写:

open class Foo {
    open val x: Int get { …… }
}

class Bar1 : Foo() {
    override val x: Int = ……
}

数据类

我们常常会创建一个只包含数据的类,其他什么事情都不做。 在Kotlin中,这样的类叫做数据类,表示关键字为data:

data class User(val name: String, val age: Int)

编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:

  • equals() / hashCode()
  • toString() 格式如 “User(name=John, age=42)”
  • componentN() functions 对应于属性,按声明顺序排列
  • copy() 函数是深度复制 有时我们需要复制一个对象,修改部分属性,其他属性保持不变。此时就需要copy() 函数。
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

注意:如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。

data class User(val name: String = "", val age: Int = 0)
多元赋值(解构声明赋值)
data class Person(var name: String="zhangsan", var age: Int=23)
val person = Person ("zhangsan", 23)
val (name, age) = person
// 内部实现为
//val name = person.component1()
//val age = person.component2()

var (name,_)=Person()//不需要使用的变量用下划线代替

println(person.component1()) //打印zhangsan
println(name) //打印zhangsan

来一个更直接的例子:

class Pair<K, V>(val first: K, val second: V) {
    operator fun component1(): K {
        return first
    }

    operator fun component2(): V {
        println("component2")
        return second
    }
}

fun main(vararg args:String)
{
    val pair = Pair(first = 1, second = "one")

    val (num, name) = pair

    println("num = $num, name = $name")

}

Note that the componentN() functions need to be marked with the operator keyword to allow using them in a destructuring declaration.

现在对于Map的遍历我们可以像下面这样:

fun main(args: Array<String>) {
    val map = hashMapOf<String, Int>()
    map.put("one", 1)
    map.put("two", 2)

    for ((key, value) in map) {
        println("key = $key, value = $value")
    }
}

标准库提供了 Pair 和 Triple,数据类Pair来处理双元对数据,Triple用于三元数据。

var pair = Pair("aa", "bb")
var (arg1, arg2) = pair
println("${pair.first} + ${pair.second}")
//打印 aa + bb arg1等于pair.first,arg2等于pair.second

var pair = Pair("aa" to 0, "bb" to 1)
var (arg1, arg2) = pair
println("${arg1.first} + ${arg2.second}")
//打印 aa + 1

抽象类和接口

跟Java一样,使用abstract关键字。

open class Base {
  open fun f() {}
}

abstract class Derived : Base() {
  override abstract fun f()
}

如果抽象类中含有抽象属性,再实现子类中必须将抽象属性初始化,除非子类也为抽象类。Kotlin与java一样是单继承

接口

Kotlin的接口类似于Java 8,它可以包含抽象方,以及方法的实现。

interface MyInterface {
    fun bar()    // 未实现
    fun foo() {  //已实现
      // 可选的方法体
      println("foo")
    }
}

接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性:

interface MyInterface{
    var name:String //name 属性, 抽象的
}

class MyImpl:MyInterface{
    override var name: String = "runoob" //重载属性
}

枚举类

枚举类最基本的用法是实现一个类型安全的枚举。

enum class Direction { 
    NORTH, SOUTH, WEST, EAST 
}

和Java一样,每一个枚举都是枚举类的实例,它们可以被初始化:

enum class Color(val rgb: Int) { 
    RED(0xFF0000), 
    GREEN(0x00FF00), 
    BLUE(0x0000FF) 
}

密封类(Sealed Classe)

类名前面添加 sealed 修饰符。当一个值为有限集中的类型,而不能有其它类型时,可以用密封类来表示受限的类继承结构。

虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。密封类从某种意义上说,它们是枚举类的扩展:枚举类型的值集也受到限制,但每个枚举常量仅作为单个实例存在,而密封类的子类可以包含多个实例并包含状态。这样又具备了枚举不具备的灵活性。

sealed class BaseClass {

    class Test1 : BaseClass() {
        override fun test() {
            println("Test1实例")
        }

    }
    class Test2 : BaseClass() {
        override fun test() {
            println("Test2实例")
        }
    }
    object Test3 : BaseClass() {
        override fun test() {
            println("Test3实例")
        }
    }
    open fun test() {
        println("BaseClass实例")
    }
}

fun test(instance: BaseClass)=when(instance){
    is BaseClass.Test1-> instance.test()
    is BaseClass.Test2-> instance.test()
    is BaseClass.Test3->instance.test()
}

fun main(str: Array<String>) {
    test( BaseClass.Test1() )
    test( BaseClass.Test2() )
    test( BaseClass.Test3 )
}

内部类

kotlin默认的内部类是final的,不能持有外部类的状态(属性、方法等)。用inner标记,可以访问外部成员。内部类对外部类的对象有一个引用。注意:外部类不可以访问内部类的 private 成员,在Java中却是可以访问的。

class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}
val demo = Outer().Inner().foo() // == 1

当内部类不使用inner标记时,我们可以称之为嵌套类(静态的final的):

class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

可以用对象表达式(object)创建匿名内部类,对于函数式java接口,可以使用lambda表达式创建。

fun main(args: Array<String>) {
    //对象表达式,对于接口中有一个或多个抽象方法都可用
    val r = object : Runnable {
        override fun run() {
            println("main?")
        }
    }
    //只用于函数式接口
    val s = Runnable { println("s") }
    r.run()
    s.run()
}

object类

可以使用object关键字声明一种特殊的类,这个类只有一个实例,所以这个类里面的成员默认都是static的。

object Singleton {
  private var num = 0

  fun sequence(): Int {
    num += 1
    return num
  }
}

Singleton.sequence()