kotlin学习笔记之类、对象和接口

一些关键字

在kotlin中所有的声明默认都是finalpublic的。

open

如果你想允许创建一个类的子类,需要使用open修饰符来标示这个类。此外,需要给每一个可以被重写的属性或方法添加open修饰符。

如果你重写了一个基类或者接口的成员,重写了的成员同样默认是open的。如果你想改变这一行为,阻止你的类的子类重写你的实现,可以显式地将重写的成员标注为final

// Clickable是open的,其它类可以继承它
open class RichButton: Clickable {
    
    // 这个函数是final的,不能在子类中重写它
    fun disable() {}
    
    // 这个函数是final的,不能在子类中重写它
    open fun animate() {}
    
    // 这个函数重写了一个open函数并且它本身同样是open的
    override fun click() {
        
    }
}

abstract

在kotlin中,同Java一样,可以将一个类声明为abstract的,这种类不能被实例化。一个抽象类通常包含一些没有实现并且必须在子类重写的抽象成员。抽象成员始终是open的,所以不需要显示地使用open修饰符。

// 这个类是抽象的,不能创建它的的实例
abstract class Animated {

     // 抽象方法
    abstract fun animate()

    // 抽象类中的非抽象函数并不是默认open的,但是可以标注为open的
    open fun stopAnimating() {}

    fun animateTwice() {}

}
  • 在接口中,不能使用finalopen或者是abstract
  • 接口中的成员始终是open的,不能将其声明为final
  • 如果它没有函数体它就是abstract的,但是这个关键字并不是必需的

override

override修饰符用来标注被重写的父类或者接口的方法和属性并且是强制要求。

class View: Focusable {
    override fun showOff() {
        
    }

}
修饰符 相关成员 评注
final 不能被重写 类中成员默认使用
open 可以被重写 需要明确地表明
abstract 必须被重写 只能在抽象类中使用,抽象成员不能有实现
override 重写父类或接口中成员 如果没有使用final表明,重写的成员默认是开放的

可见性修饰符:默认为public

修饰符 类成员 顶层声明
public(默认) 所有地方可见 所有地方可见
internal 模块中可见 模块中可见
protected 子类中可见
private 类中可见 文件可见

补充:一个模块就是一组一起编译的kotlin文件。这有可能是一个Intellij IDEA模块、一个eclipse项目、一个maven或gradle项目或者一组使用调用ant任务进行编译的文件。

kotlin的类和接口与Java的类和接口有一点区别的。例如,接口可以包含属性声明。与Java不同,kotlin的声明默认是final和public的,而且代码更加简洁。此外,嵌套的类默认并不是内部类:它们并没有包含对其外部类的隐式引用。

以下是一个简单类的声明和使用

// java的类
public class Persion {
    private final String name;
    
    public Persion(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

// 对应的kotlin类,括号表示主构造方法
class Person(val name: String)       // 值对象

// 实例化
val persion = Persion("张三")
println(p.name) // 张三

在Java中一个类可以声明一个或多个构造方法。kotlin也是类似的只是做出了一点修改:区分了主构造方法(通常是主要而简洁的初始化类的方法,并且在类体外部声明)和从构方法(在类体内部声明)。同样也允许在初始化语句块中添加额外的初始化逻辑。

class Persion(name:String) {
    val nickname:String

    init {
        nickname = name
    }
}

class User constructor(_nickname: String) {
    val inckname: String

    init {
//        this.inckname = _nickname
        inckname = _nickname
    }
}

class Student(_name: String) {
    val name = _name
}

fun main(args: Array) {
    val p = Persion("张三")
    println(p.nickname)

    val user = User("李四")
    println(user.inckname)

    val student = Student("小明")
    println(student.name)
}
// 张三
// 李四
// 小明

open class User(val nickname: String) {}
// 初始化父类
class TwitterUser(nickname: String): User(nickname) {}
  • 此时的主构造方法的参数并没有使用关键字valvar,那么它不能这样使用p.name,如果加上可以简化代码
  • 可以为构造方法的参数设置默认值
  • constructor关键字用来开始一个主构造方法或从构造方法的声明
  • init关键字用来引入一个初始化语句块
  • 因为主构造方法有语法限制,不能包含初始化代码,因此,constructorinit会一起使用
  • 构造方法的参数带个_(下划线)主要是用来区分属性的名字和构造方法参数的名字,也可以使用this关键字类区分
  • 如果你的类具有一个父类,主构造方法同样需要初始化父类
  • kotlin的类也具有默认构造方法
  • 如果你想要确保你的类不被其他代码实例化,必须把构造方法标记为private

接着是从构造方法。使用多个构造方法的类在kotlin代码中不如在Java中常见。大多数在Java中需要重载构造方法的场景都被kotlin支持参数默认值和提供参数命名的语法涵盖了。

class MyButton {
    
    // 使用super调用父类构造方法,也可以在主构造方法中调用
    constructor(ctx: Context) {}

    constructor(ctx: Context, attr: AttributeSet) {}
           
}

// 带主构造方法
class View(val name: String){
    constructor(ctx: Context): this("") {}
    
    constructor(ctx: Context, attr: AttributeSet): this("") {}
}

小贴士:不要声明多个从构造方法用来重载和提供参数的默认值。取而代之的是,应该直接表明默认值。

属性

class Person(
    val name: String,// 只读属性:生成一个字段和一个简单的getter
    var isMarried: Boolean// 可写属性:一个字段、一个getter和一个setter
)

fun main(args: Array) {
    val person = Person("张三", true)
    println(person.name)
    println(person.isMarried)
    person.isMarried = false;
    println(person.isMarried)
}
// 张三
// true
// false

// 自定义setter和getter
class Person {

    // val声明的属性只有getter
    val name: String
        get() {
            return "张三"
        }

    val address: String
        get() = "黄山"

    // var声明的属性有getter和setter
    var age: Int = 0
        get() {
            return field
        }
        set(value: Int) {
            field = value
        }
}

fun main(args: Array) {
    val person = Person()
    println(person.name)
    println(person.address)
    println(person.age)
    person.age = 22
    println(person.age)
}

setter的函数体中,使用了特殊的标识符field(可以简单理解为就是该属性自身)来访问支持字段的值。在getter中,只能读取值;而在setter中,既能读取它也能修改它。另外,可以修改访问器的可见性。

类的继承结构

  • 使用interface声明接口
  • 使用:来代替Java中的extendsimplements
  • 一个类可以实现任意个接口,但是只能继承一个类
  • override修饰符用来标注被重写的父类或者接口的方法和属性且是强制要求
  • 接口的方法可以有一个默认实现,在实现该接口是可以重新定义,也可以直接省略它
  • 明确使用父类的方法可以用这种方式——super<父类>.方法
interface Clickable {
    fun click()

    // 带默认实现的方法
    fun showOff() = println("I'm clickable!")
}

class Button: Clickable {
    override fun click() {
        println("I was clicked")
    }
}

如果实现两个或两个以上的接口中有相同的方法,实现类必需显式地将其实现,即使它们有默认的实现,否则编译不通过。

interface Clickable {
    fun click()
    // 默认实现
    fun showOff() = println("Clickable")
}

interface Focusable {
    // 默认实现
    fun showOff() = println("Focusable")
}

class Button : Clickable, Focusable {
    override fun click() {
        println("I was clicked")
    }

    // 必须提供自己的实现
    override fun showOff() {

    }
}

在kotlin中,接口可以包含抽象属性声明。

interface User {
    val nickname: String
}

接口本身并不包含任何状态,因此只有实现这个接口的类在需要的情况下会存储这个值。

// 主构造方法属性
class PrivateUser(override val nickname: String): User

// 自定义getter
class SubscribingUser(val email: String): User {
    override val nickname: String
        get() = email.substringBefore('@')

}

// 属性初始化
class FacebookUser(val accountId: Int): User {
    override val nickname: String = getFacebookName(accountId)
}

除了抽象属性声明外,接口还可以包含具有getter和setter的属性,只要它们没有引用一个支持字段(支持字段需要在接口中存储状态,而不是允许的)。

interface User {
    // 必须在子类中重写
    val email: String
    // 可以被继承
    val nickname: String
        get() = email.substringBefore('@')
}

当继承是一个普通类时,被继承的类名后面必须要有小括号(),代表该类的构造方法,与接口的不同点。

// open关键字已经在前面讲述了
open class View {
    constructor(ctx: Context) {}

    constructor(ctx: Context, attr: AttributeSet) {}
}

class MyButton : View {

    // 使用super调用父类构造方法,也可以在主构造方法中调用
    constructor(ctx: Context): super(ctx) {

    }

    constructor(ctx: Context, attr: AttributeSet): super(ctx, attr) {

    }
}

内部类和嵌套类:默认是嵌套类

像Java一样,在kotlin中可以在另一个类中声明一个类。这样做在封装一个辅助类或者把一些代码放在靠近它的被使用的地方时非常有用。区别是kotlin的嵌套类不能访问外部类的实例,除非你特别要求。

kotlin中没有显示修饰符的嵌套类与Java中的static嵌套类是一样的。要把它变成一个内部类来持有一个外部类的引用的话需要使用inner修饰符。

类A在另一个类B中的声明 在Java中 在kotlin中
嵌套类(不存储外部类的引用) static class A class A
内部类(存储外部类的引用) class A inner class A

在kotlin中引用外部类实例的语法也与Java不同。需要使用this.@Outerinner类出访问Outer类:

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

密封类

when表达式中,kotlin编译器会强制检查默认选项。对于sealed类的子类,不需要提供默认分支。另外,sealed修饰符隐含的这个类是一个open类,不需要显式添加open修饰符。

// 将基类标记为密封
sealed class Expr {
    // 将所有可能的类作为嵌套类列出
    class Num(val value: Int) : Expr()

    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
        when (e) {
            // when表达式涵盖了所有可能的情况,所以不再有可能的情况,所以不再需要else分支
            is Expr.Num -> e.value
            is Expr.Sum -> eval(e.right) + eval(e.left)
        }

通用对象方法

就像Java中的情况一样,所有的kotlin类也有许多也许你想重写的方法:toString、equals和hashCode。

字符串表示:toString()

默认来说,一个对象的字符串表示形如Client@5e9f23b4,这并不十分有用。要想要改变它,需要重写toString方法。

class Client(val name: String,val postalCode: Int) {
    override fun toString(): String {
        return "Client(name=$name,postalCode=$postalCode)"
    }
}

对象相等性:equals()

在Java中,可以使用==运算符来比较基本数据类型和引用类型。如果应用在基本类型上,它比较的是值,然而在引用类型上比较的是引用。

在kotlin中,==运算符是比较两个对象的默认方式:本质上说它就是通过调用equals来比较两个值。因此,如果equals在你的类中被重写了,你能够很安全地使用==来比较实例。要想进行引用比较,可以使用===运算符,这与Java中的==比较对象引用的效果一模一样。

class Client(val name: String, val postalCode: Int) {
    override fun toString(): String {
        return "Client(name=$name,postalCode=$postalCode)"
    }

    // “Any”是java.lang.Object的模拟:kotlin中所有类的父类。“Any?”意味着“other”是可以为空
    override fun equals(other: Any?): Boolean {
        // 检查“other”是不是一个Client
        if (other == null || other !is Client)
            return false
        
        // 检查对应的属性是否相等
        return name == other.name && postalCode == other.postalCode
    }
}

kotlin中的is检查是Java中instanceof的模拟,用来检查一个值是否为一个指定的类型。

Hash容器:hashCode()

  • hashCode方法通常与equals一起被重写
  • 如果两个对象相等,它们必须有着相同的hash
class Client(val name: String, val postalCode: Int) {
    override fun toString(): String {
        return "Client(name=$name,postalCode=$postalCode)"
    }

    // “Any”是java.lang.Object的模拟:kotlin中所有类的父类。“Any?”意味着“other”是可以为空
    override fun equals(other: Any?): Boolean {
        // 检查“other”是不是一个Client
        if (other == null || other !is Client)
            return false

        // 检查对应的属性是否相等
        return name == other.name && postalCode == other.postalCode
    }

    override fun hashCode(): Int {
        return name.hashCode() * 31 + postalCode
    }
}

数据类

如果为你的类添加data修饰符,必要的方法将会自动生成好。如equalshashCodetoString

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

一个允许copy类的实例方法,并在copy的同时修改某些属性的值。创建副本通常是修改实例的好选择:副本有着单独的声明周期而且不会影响代码引用原始实例的位置。

class Client(val name: String, val postalCode: Int) {
    override fun toString(): String {
        return "Client(name=$name,postalCode=$postalCode)"
    }

    // “Any”是java.lang.Object的模拟:kotlin中所有类的父类。“Any?”意味着“other”是可以为空
    override fun equals(other: Any?): Boolean {
        // 检查“other”是不是一个Client
        if (other == null || other !is Client)
            return false

        // 检查对应的属性是否相等
        return name == other.name && postalCode == other.postalCode
    }

    override fun hashCode(): Int {
        return name.hashCode() * 31 + postalCode
    }

    fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode)
}

fun main(args: Array) {
    val bob = Client("bob", 973293)
    println(bob.copy(postalCode = 382555))
}
// Client(name=bob,postalCode=382555)

类委托

可以使用by关键字将接口的实现委托到另一个对象。

// 将MutableCollection的实现委托给innerSet
class CountingSet(val innerset: MutableCollection = HashSet())
    : MutableCollection by innerset {
    var objectAdded = 0

    // 不使用委托,提供一个不同的实现
    override fun add(element: T): Boolean {
        objectAdded++
        return innerset.add(element)
    }

    // 不使用委托,提供一个不同的实现
    override fun addAll(elements: Collection): Boolean {
        objectAdded += elements.size
        return innerset.addAll(elements)
    }
}

对象声明

对象声明是定义单例的一种方式。它可以包含属性、方法、初始化语句块等的声明。唯一不允许的就是构造方法。

object Payroll {
    val allEmployees = arrayListOf()
    
    fun calculateSalary() {
        for (person in allEmployees) {}
    }
}

// 使用对象名加.字符的方式来调用方法和访问属性
    Payroll.allEmployees.add(Person())
    Payroll.calculateSalary()

伴生对象

companion关键字用来声明伴生对象。伴生对象可以访问类中的所有private成员,包括private构造方法,它是实现工厂模式的理想选择。

class User private constructor(val nickname: String) {
    companion object {
        fun newSubscribingUser(email: String) {
            User(email.substringBefore('@'))
        }
        
        fun newFacebookUser(accountId: Int) {
            User(getFacebookName(accountId))
        }
    }
}

val subscriptingUser = User.newSubscribingUser("[email protected]")
val facebookUser = User.newFacebookUser(4)

伴生对象是一个声明在类中的普通对象。它可以有名字,实现一个接口或者有扩展函数或属性。

interface JSONFactory {
    fun fromJSON(jsonText: String): T
}

class P {
    companion object Loader: JSONFactory {
        override fun fromJSON(jsonText: String): Persion {
            
        }

    }
}

// 伴生对象没名字时
//fun P.Companion.t() = 123

// 伴生对象有名字时
fun P.Loader.m() = 123

对象表达式

object关键字不仅仅能用来声明单例式对象,还能用来声明匿名对象。kotlin的匿名对象可以存储到一个变量中,可以实现多个接口或者不是实现接口,可以修改变量的值。

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent?) {

    }
})

val listener = object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent?) {

    }
}

fun countClicks(window: Window) {
    // 局部变量
    var clickCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent?) {
            // 更新变量值
            clickCount++
        }
    })
}

你可能感兴趣的:(kotlin学习笔记之类、对象和接口)