Kotlin基础知识一:classes, objects和interfaces

一、interfaces

普通的接口声明:

interface Clickable {
    fun click()
}

实现该接口时,override修饰符不可省略,这点与Java不同。

class Button : Clickable {
    override fun click() {
        TODO("Not yet implemented")
    }
}

接口中定义的方法可以有默认的实现:

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

如果两个接口定义了同样的方法时

interface Focusable {
    fun setFocus(b:Boolean) =
        println("I ${if (b) "got" else "lost"} focus.")

    fun showOff() = println("I'm focusable!")
}

编译器强制继承这两个接口的类必须实现这个相同的方法:

class Button : Clickable, Focusable {
    override fun click() = println("I was clicked")
    override fun showOff() {
        super.showOff() //等同于Java的Clickable.super.showOff()
        super.showOff() //等同于Java的Focusable.super.showOff()
    }
}

二、access修饰符:classes和methods 默认是final的

Kotlin的classes和methods默认修饰符是final,这就意味着如果要允许子类存在,需要手动使用open修饰符。

//open class, 允许其它类继承.
open class RichButton : Clickable, Focusable {
    fun disable() {} //方法默认为final,表示不可再子类中override.

    open fun animate() {} //open方法,可以在子类中override.

    override fun click() {} //overrides自一个open方法,因此也是open方法.

    override fun showOff() {
        super.showOff()
        super.showOff()
    }

    //手动标识为final方法,表示不可在子类中override.
    final override fun setFocus(b: Boolean) {
        super.setFocus(b)
    }
}

data class

data class 需要满足一下要求:

  • primary constructor至少要有一个参数;
  • 所有的primary constructor参数都需要被标记为val或者var;
  • data class 不能是abstract, open, sealed, 或者inner的.
data class User(val name: String, val age: Int)

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

data class的参数可以有默认值,从最后一个参数起,如果有默认值的话,那么实例化时可以不用传参。

data class User(val name: String = "", val age: Int = 20)
val defaultUser = User("Default")
abstract class Animated {
    abstract fun animate() //abstract方法, 必须被子类override.
    open fun stopAnimating() {} //open方法,可以被子类override.
    fun animateTwice() {} //final方法, 不可以被子类override.
}

sealed class

sealed class是带继承限制的abstract class。继承该sealed class的类必须和该sealed class在同一文件内。
从而就限制了在外部文件中实现该sealed class的子类的可能,提高了安全性。
举例:

abstract class LoadState

data class Success(val dataFetched: String?): LoadState()
data class Error(val exception: Exception):LoadState()
object NotLoading: LoadState()
object Loading: LoadState()

fun getStateOutput(loadState:LoadState){
    return when(loadState) {
        is Error -> {
            println(loadState.exception.toString())
        }
        is Success -> {
            //If the dataFetched is null, return a default string.
            println(loadState.dataFetched ?: "Ensure you startFetch first")
        }
        is Loading -> {
            println("loading...")
        }
        is NotLoading -> {
            println("IDLE")
        }
        //you have to add an else branch because the compiler cannot know whether the abstract class is exhausted.
        else -> println("invalid")
    }
}

如果换成sealed class来实现:


import java.io.IOException

sealed class LoadState
sealed class Error : LoadState() {
    data class CustomIOException(val ioException: IOException) : Error()
    data class CustomNPEException(val npeException: NullPointerException) : Error()
}

data class Success(val dataFetched: String?) : LoadState()
object NotLoading : LoadState()
object Loading : LoadState()


fun getStateOutput(loadState: LoadState) {
    return when (loadState) {
        is Success -> {
            //If the dataFetched is null, return a default string.
            println(loadState.dataFetched ?: "Ensure you startFetch first")
        }
        is Loading -> {
            println("loading...")
        }
        is NotLoading -> {
            println("IDLE")
        }
        is Error.CustomIOException -> {
            println(loadState.ioException.toString())
        }
        is Error.CustomNPEException -> {
            println(loadState.npeException.toString())
        }
       //这里不需要else,因为LoadState的所有继承类都在文件中枚举了,不可能出现其他的继承类。
        else -> {
            //'when' is exhaustive so 'else' is redundant here
        }
    }
}

三、visibility修饰符:默认public

Kotlin visibility modifiers

注意点:

  1. a module usually consists of several packages, and different modules may contain declarations from the same package.
  2. an outer class doesn’t see private members of its inner (or nested) classes in Kotlin.

四、内部类(inner class)

Kotlin中内部类默认不持有外部类的引用(reference),相当于Java的static class,如果需要持有外部类的引用,需要手动添加inner关键字。


Java和Kotlin中的嵌套类和内部类对比

举例:

class Outer {

    inner class Inner {
        fun getReference(): Outer = this@Outer
    }
}

五、Primary constructor和Secondary constructors

使用圆括号阔起constructor parameters,这个称作Primary constructor。

//“val” means the corresponding property is generated for the constructor parameter
class User(val nickname: String) 

带有默认值的构造方法参数:

//Provides a default value for the constructor parameter
class User(val nickname: String, val isSubscribed: Boolean = true)

Secondary constructor的例子:


Secondary constructor

继承关系:


继承关系

六、在接口中声明抽象属性(abstract property)

如下所示:

interface User {
    val nickName: String
}

这意味着User接口的实现类需要提供获取nickName值的方式,interface本身没有规定是存储于backing field还是通过getter获取。
以下是几种使用方式:

class PrivateUser(override val nickName: String) : User //Primary constructor property
class SubscribingUser(val email: String) : User {
    //Implement members
    override val nickName: String //Custom getter
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: Int) : User {
    override val nickName = getFacebookName(accountId) //Property initializer

}

另外,interface还可以包含带有getters和setters的properties,只要这些properties不持有backing field。

interface User {
    val email: String
    val nickName: String //Property doesn’t have a backing field: the result value is computed on each access.
        get() = nickName.substringBefore('@')
}
在setter中访问the backing field
class Account(val name: String) {
    var address: String = "unspecified"
        set(value: String) {
            println(
                """Address was changed for $name:
                "$field" -> "$value".""".trimIndent() //Reads the backing field value
            )
            field = value //Updates the backing field value
        }
}

fun main() {
    val account = Account("Tom")
    println(account.address)
    account.address = "ShangHai"
    println(account.address)
    account.address = "BeiJing"
    println(account.address)
}

另外一个例子:

class Sword(_name: String) {
    var name = _name
        get() = "The legacy $field"
        set(value) {
            println("set value: $value, field: $field")
            field = value.toLowerCase().reversed().capitalize()
        }

    init {
        println("init : $_name")
        name = _name
    }
}

fun main(args: Array) {
    val s = Sword("Chris")
    println(s.name)

    println("-----------")
    s.name = "Haha"
    println(s.name)
}

输出:

init : Chris
set value: Chris, field: Chris
The legacy Sirhc
-----------
set value: Haha, field: Sirhc
The legacy Ahah
修改set/get的可见性
class LengthCounter {
    var counter: Int = 0
        private set

    fun addWord(word: String) {
        counter += word.length
    }
}

fun main() {
    val lengthCounter = LengthCounter()

    lengthCounter.addWord("Hello")
    println(lengthCounter.counter)

    lengthCounter.addWord("World")
    println(lengthCounter.counter)

    lengthCounter.counter += 10 //compile error: Cannot assign to 'counter': the setter is private in 'LengthCounter'
}
通用的object方法
  1. String representation: toString()
  2. Object equality: equals()
    Kotlin中的"=="等价于equals()方法。
  3. Hash containers: hashCode()
class Client(val name: String, val postCard: Int) {

    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client) {
            return false
        }

        return name == other.name && postCard == other.postCard
    }

    override fun toString(): String {
        return "Client(name=$name, postCard = $postCard)"
    }

    override fun hashCode(): Int = name.hashCode() * 31 + postCard
}

fun main() {
    val client1 = Client("Alice", 342562)
    val client2 = Client("Alice", 342562)
    println(client1 == client2) //true
    val processed = hashSetOf(Client("Alice", 342562))
    println(processed.contains(Client("Alice", 342562))) ///true

}

注意:

  1. The hashCode method should be always overridden together with equals.
  2. if two objects are equal, they must have the same hash code.

七、data class:自动生成通用的object方法

data class Client(val name: String, val postCard: Int)

fun main() {
    val processed = hashSetOf(Client("Alice", 342562))
    println(processed.contains(Client("Alice", 342562))) //true
}

注意:不在Primary constructor中声明的properties不参与equality的校验和hash code的计算。

定义data class需要遵循的原则:

  1. primary constructor至少要有一个参数;
  2. 所有的primary constructor参数都需要标记为var或val;
  3. data class不能是abstract, open, sealed, 或 inner;
  4. data class 不能继承自其它的classes,但是可以实现interfaces。

八 使用"by"关键字进行类委托(class delegation)

举例如下:

class DelegatingCollection : Collection {
    private val innerList = arrayListOf()
    override val size: Int get() = innerList.size

    override fun contains(element: T): Boolean = innerList.contains(element)

    override fun containsAll(elements: Collection): Boolean =
        innerList.containsAll(elements)

    override fun isEmpty(): Boolean = innerList.isEmpty()

    override fun iterator(): Iterator = innerList.iterator()
}

其中DelegatingCollection类通过持有innerList实例,这就是常见的Decorator模式。
可以简写为:

class DelegatingCollection(innerList: Collection = ArrayList()) :
    Collection by innerList {}

如果想改变某些方法的默认实现,可以重写这些方法。

class CountingSet(val innerSet: MutableCollection = HashSet())
    :MutableCollection by innerSet {

    var objectsAdded = 0
    override fun add(element: T): Boolean {
        objectsAdded++
        return innerSet.add(element)
    }

    override fun addAll(elements: Collection): Boolean {
        objectsAdded += elements.size
        return innerSet.addAll(elements)
    }
}

九、object关键字

object关键共出现在kotlin的三种场景中,他们所共有的核心思想是:
定义一个class、同时创建这个class的一个实例。

  1. object declaration是定义单例的一种方式;
  2. companion objects用来包含类的静态方法;
  3. object expression用来替换Java的匿名内部类(anonymous inner class)。The “object” keyword: declaring a class and creating an instance, combined。
object declaration

先看第一种:

object PayRoll {
    val allEmployees = arrayListOf()

    fun calculateSalary() {
        for(person in allEmployees) {
            ...
        }
    }
}

object declaration将class的声明与该class的单一实例的声明组合在一起。object declaration可以声明properties、methods、initializer blocks等等,但是不允许有constructors。
object declaration在定义的时候就会创建类的实例,而不是通过调用构造方法来实现,这一点与常规的classes不同。
举例:

object CaseInsensitiveFileComparator : Comparator {
    override fun compare(file1: File, file2: File): Int {
        return file1.path.compareTo(
            file2.path,
            ignoreCase = true
        )
    }
}

fun main() {
    println(CaseInsensitiveFileComparator
      .compare(File("/User"), File("/user"))) //0

    val files = listOf(File("/Z"), File("/a"))
    println(files.sortedWith(CaseInsensitiveFileComparator)) //[/a, /Z]
}

在Java中使用Kotlin的单例的方法:

/* Java */
CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);
companion objects

companion object可以访问外部类的private成员,包括private constructor,因此这是实现Factory模式的较为理想的方式。
举例:

class User {
    val nickName:String

    constructor(email:String) {
        nickName = email.substringBefore('@')

    }

    constructor(facebookAccountId:Int) {
        nickName = getFacebookName(facebookAccountId)
    }
}

改为使用factory方法来创建类的实例:

class User2 private constructor(val nickName: String) {
    companion object {
        fun newSubscriberUser(email:String) =
            User(email.substringBefore('@'))

        fun newFacebookUser(accountId:Int) =
            User(getFacebookName(accountId))
    }
}

fun main() {
    val subscribingUser = User2.newSubscriberUser("[email protected]")
    val facebookUser = User2.newFacebookUser(4)
}
在companion object中实现接口
为一个companion object定义扩展函数、
// business logic module
class Person(val firstName: String, val lastName: String) {
    companion object {
    }
}
// client/server communication module
fun Person.Companion.fromJSON(json: String): Person {
}
object expressions: 匿名内部类的另一种表述

object expression声明一个class、并且创建一个instance,但是不会为这个class或instance分配名字。通常来说,这个object是用作函数调用的的一个参数。

与Java不同的是,Kotlin的anonymous object可以实现多个接口。
anonymous objects不是单例的,每执行一次object expression,就会创建一个实例。

object expressions大多用在需要在匿名类中重写多个方法的时候。
object关键字还可以用来声明匿名内部类, 该匿名内部类不像java只能实现一个接口或继承一个对象,它可以实现多个接口。

nterface SchoolWork {
    fun getCore()
}

interface HomeWork {
    fun getProgress()
}

fun main(args: Array) {
    doingHomeWork(object : HomeWork, SchoolWork {
        override fun getCore() {
        }

        override fun getProgress() {
        }
    })

    doingSchoolWork(object : HomeWork, SchoolWork{
        override fun getCore() {
        }

        override fun getProgress() {
        }

    })
}

同时,也可以定义一个匿名类对象的变量:

interface TaskStatusListener {
    fun onFailure();
    fun onSuccess();
}

val taskStatus = object : TaskStatusListener {
        override fun onSuccess() {
            println("onSuccess called")
        }

        override fun onFailure() {
            println("onFailure called")
        }
}

它还可以访问外部函数创建的变量,并且不用标识为final类型:


interface TaskStatusListener {
    fun onFailure();
    fun onSuccess();
}


fun getTaskStatus(status: TaskStatusListener) {
    status.onFailure();
    status.onSuccess();
}

fun main(args: Array) {

    var count = 0;
    val taskStatus = object : TaskStatusListener {
        override fun onSuccess() {
            count++;
            println("onSuccess called")
        }

        override fun onFailure() {
            count++
            println("onFailure called")
        }
    }
    getTaskStatus(taskStatus)

    getTaskStatus(object : TaskStatusListener {
        override fun onSuccess() {
            count++;
            println("onSuccess called")
        }

        override fun onFailure() {
            count++
            println("onFailure called")
        }
    })
    println("count: $count")

}

你可能感兴趣的:(Kotlin基础知识一:classes, objects和interfaces)