Kotlin知识总结:类、对象和接口

类、对象和接口

接口

kotlin接口可以包含抽象方法的定义以及非抽象方法的实现(与 Java 8 中的默认方法类似),但它们不能包含任何状态,通过interface 关键字定义,可以有一个默认实现

interface Clickable {
	fun click ()
 	fun showoff() = println ("clickable!")
}

如果多个接口,每一个都包含了带默认实现的 showoff 方法,如果要调用,需要显示实现,

super<Clickable>. showoff ()

可见性修饰符

Java 的类和方法默认是 open 的,而 Kotlin 中默认都是 final 的。
如果想创建一个类的子类,就需要用open修饰符来标示这个类,需要给每一个可以被重写的属性或方法添加 open 修饰符。如果你重写了一个基类或者接口的成员,重写了的成员同样默认是open 的 。 如果你想改变这一行为,阻止你的类的子类重写你的实现,可以显式地将重写的成员标注为 final 。抽象成员始终是 open 的, 所以不需要显式地使用 open 修饰符.在接口中 , 不能使用final、 open 或者是 abstract。 接口中的成员始终是 open 的,
不能将其声明为 final 的
在这里插入图片描述
public :公开,可见性最大,哪里都可以引用。
private:私有,可见性最小,根据声明位置不同可分为类中可见和文件中可见。
protected:保护,相当于 private + 子类可见。
internal:内部,仅对 module 内可见。
比Java少了一个default 「包内可见」,多了一个internal「module 内可见」。比如Android Studio 里的 module。默认是public。protected 成员只在类和它的子类中可见,类的扩展函数不能访问它的 private 和 protected 成员。Kotlin 中一个外部类不能看到其内部(或者嵌套)类中的 private 成员
在这里插入图片描述

内部类和嵌套类

Kotlin 的嵌套类不能访问外部类的实例,要把它变成一个内部类来持有一个外部类的引用的话需要使用 inner 修饰符。
在这里插入图片描述
需要使用 this@Outer 从Inner 类去访问 Outer 类


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

但是,嵌套函数中可以访问在它外部的所有变量或常量,例如类中的属性、当前函数中的参数与变量等。

fun login(user: String, password: String, illegalStr: String) {
     
    fun validate(value: String, illegalStr: String) {
      if (value.isEmpty()) {
          throw IllegalArgumentException(illegalStr)
      }
    }
 
    validate(user, illegalStr)
    validate(password, illegalStr)
}

改进:

fun login(user: String, password: String, illegalStr: String) {
    fun validate(value: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(illegalStr)
        }
    }
    ...
}

validate()可以直接访问illegalStr

为父类添加一个 sealed修饰符,对可能创建的子类做出严格的限制。所有的直接子类必须嵌套在父类中 。就是说,当你在 when 中使用 sealed 类并且添加 一个新的子类的时候,有返回值的when 表达式会导致编译失败,它会告诉你哪里的代码必须要修改

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) {
            is Expr.Num -> e.value
            is Expr.Sum -> eval(e.right) + eval(e.left)
        }
}

构造函数

class User constructor(_nickname: String) {
    val nickname: String
    init {
        nickname = nickname
    }
}

主构造函数:在类名之后加上constructor,用来开始一个主构造方法或从构造方法的声明。 init 关键字用来引入一个初始化语句块。init 代码块是紧跟在主构造器之后执行的,这是因为主构造器本身没有代码体,init 代码块就充当了主构造器代码体的功能。主构造方法有语法限制,不能包含初始化代码。如果类中有主构造器,那么其他的次构造器都需要通过 this 关键字调用主构造器,可以直接调用或者通过别的次构造器间接调用

class User constructor(var name: String) {
     //  直接调用主构造器
    constructor(name: String, id: Int) : this(name) {
    }
     // 通过上一个次构造器,间接调用主构造器
    constructor(name: String, id: Int, age: Int) : this(name, id) {
    }
}

init 代码块是先于次构造器执行的。如果主构造方法没有注解或可见性修饰符,同样可以去掉 constructor 关键字

class User(_nickname: String) {
	val nickname = nickname
}

如果有注解或可见性修饰符,就不可省略

class User private constructor(name: String) {
//主构造器被修饰为私有的,外部就无法调用该构造器
}

如果属性用相应的构造方法参数来初始化,代码可以通过把 val或var 关键字加在参数前的方式来进行简化

class User(val nickname: String)
//val 意味着相应的属性会用构造方法的参数来初始化.就等价于在类中创建了该名称的属性(property),并且初始值就是主构造器中该参数的值

可以给构造函数里的参数加上默认值。

class User (val nickname: String,val isSubscribed : Boolean = true)
//为 isSubscribed  参数使用默认值“true”
val alice = User ("Alice")
//可以按照声明顺序写明所有的参数
val bob = User ("Bob", false)
//可以显式地为某些构造方法参数标明名称
val carol =User ("Carol", isSubscribed  = false)

属性

接口可以包含抽象属性声明,但是接口本身并不包含任何状态,因此只有实现这个接口的类在需要的情况下会存储这个值。

interface User {
val nickname: String
}

可以在实现类中赋值

override val nickname: String
get() =.....

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

interface User {
val email: String
val nickname: String
get () = ....
}

属性没有支持字段:结果值在每次访问时通过计算或其他方式获得。
val 和 Java 中的 final 类似,表示只读变量,不能修改,但是可以通过getter()来修改返回值

val size: Int
    get() { //  每次获取 size 值时都会执行 items.size
        return items.size
    }

在setter 的函数体中,使用了特殊的标识符 field 来访问支持字段的值。在
getter 中,只能读取值;而在setter 中 , 既能读取它 也 能修改它。

object

在java中,常量可以通过static final来定义,Kotlin中,静态变量和静态方法这两个概念被去除了。可以通过companion object来达到相同的效果

class Sample {
    ...
    companion object {
        val anotherString = "Another String"
    }
}

“object ”关键字:将声明一个类与创建一个实例结合起来,也就是说,创建一个类,并且创建一个这个类的对象。如果要使用它,直接通过类名来访问:Sample.name。
单例
在java中单例这样写:

public class A {
    private static A sInstance;
    
    public static A getInstance() {
        if (sInstance == null) {
            sInstance = new A();
        }
        return sInstance;
    }

    ...
}

Kotlin中可以这样写

// class 替换成了 object
object A {
    val number: Int = 1
    fun method() {
        println("A.method()")
    }
}

这种通过 object 实现的单例是一个饿汉式的单例,并且实现了线程安全
object 声明的类,不允许使用构造方法(包括主构造方法和从构造方法)。因为在定义的时候就立即创建了,不需要在代码的其他地方调用构造方法 。 因此,定义一个构造方法是没有意义的。
匿名类
在Java中,匿名类是这样写的,

ViewPager.SimpleOnPageChangeListener listener = new ViewPager.SimpleOnPageChangeListener() {
    @Override // 
    public void onPageSelected(int position) {
        // override
    }
};

Kotlin中,是这样写的

val listener = object: ViewPager.SimpleOnPageChangeListener() {
    override fun onPageSelected(position: Int) {
        // override
    }
}  

伴生对象
Kotlin 中的类不能拥有静态成员,作为替代, Kotlin 依赖包级别函数(在大多数情形下能够替代 Java 的静态方法)和对象声明(在其他情况下替代 Java 的静态方法,同时还包括静态字段) 。但是顶层函数不能访问类的 private成员。通过关键字companion,获得了直接通过容器类名称来访问这个对象的方法和属性的能力

class A {
    companion object {
        fun bar() {
            println ("Companion object called")
        }
    }
}

A.bar()

一个类中最多只可以有一个伴生对象。
java中的静态初始化,在Kotlin中也可以实现,

class Sample {
       
    companion object {
       
        init {
            ...
        }
    }
}

工厂模式:

//伴生对象,工厂模式
class UserCom private constructor(val nickName: String) {
    companion object {
        fun newSubscribingUser(email: String) =
            UserCom(email)

        fun newSubscribingUser2(email2: String) =
            UserCom(email2)
    }
}

数据类

在开发中,经常获取网路数据,需要定义数据类来解析(可以用JsonToKotlin,方便转换),在java中,会有很多get/set方法,在kotlin中,通过data标识符,可以实现相同的效果:

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

重写了:
• equals 用来比较实例
• hashCode 用来作为例如 HashMap 这种基于哈希容器的键
• toString 用来为类生成按声明顺序排列的所有字段的字符串表达形式
数据类有一些限制:
• 主构造函数需要至少有一个参数;
• 主构造函数的所有参数需要标记为 val 或 var;
• 数据类不能是抽象、开放、密封或者内部的;
数据类的属性,强烈建议使用val来定义。数据类可以通过copy()来复制副本,副本有着单独的生命周期而且不会影响代码中引用原始实例的位置。

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
或者
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

类委托

如果要实现一个接口,可以使用 by 关键字将接口的实现委托到另一个对象 。官网上给的例子

interface Base {
fun print()
} 
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
} 
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}

看一个覆盖的例子

interface Base {
    fun printMessage()
    fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
    override fun printMessage() { println(x) }
    override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
    override fun printMessage() { println("abc") }
}

fun main(){
   val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
    b.printMessage()
    b.printMessageLine()
    }
输出:
abc
10
10
10

可以看出,这种方式重写的成员不会在委托对象的成员中调用 , 委托对象的成员只能访问其自身对接
口成员实现

委托属性:val/var <属性名>: <类型> by <表达式> 。 在 by 后边的表达式是该 委托,因为属性对应
的 get() (与 set() ) 会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现
任何的接口, 但是需要提供一个 getValue() 函数 (与 setValue() -对于 var 属性)

class Example {
var p: String by Delegate()
}

class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
} 
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}

委托属性,就是可以把一些常见的属性类型,比如:
延迟属性 (lazy properties) : 其值只在首次访问时计算;
可观察属性 (observable properties) : 监听器会收到有关此属性变更的通知;
把多个属性储存在一个映射 (map) 中, 而不是每个存在单独的字段中。
把get和set方法的一些处理,封装成一个库或者公共类,以后的处理可以通过by 来委托给这个库或公共类

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章