Kotlin学习笔记(一) 类与对象

类与对象

1. 属性与字段

  • Getters与Setters

set方法与get方法中如果需要用到变量,需要用field来表示当前变量而不能直接引用,否则会造成死循环。

关键字field表示幕后字段,只能在gettersetter内对它访问。

// 正确方法
var gender: String = "Man"
    set(value) {
        println("setter $value")
        field = value
    }

// 错误方法
var gender: String = "Man"
    set(value) {
        println("setter $value")
        gender = value
    }

2. 可见性修饰符

private:不指定任何可见行修饰符,默认为public

protected:只在声明他的文件内可见

internal:在相同模块内随处可见

public:不适用与顶层声明

注:要使用另一包中可见的顶层声明,仍需要将其导入进来。

  • 类和接口

private:只在该类的内部(包括所有成员)可见

protected:类内部与子类中可见

internal:能见到类声明的本模块内的任何客户端都可见

public:能见到类声明的任何客户端

注:覆盖一个protected成员并没有显式指定可见性,该成员仍为protected

  • 构造函数

构造函数默认都是public

// 指定一个类的主构造函数的可见性,需要添加显式constructor关键字
class C private constructor(a: Int) {  }

3. 扩展

  • 扩展函数

扩展函数是静态分发的,它们是由扩展函数所在的表达式的类型来决定的,而不是由表达式运行时求值结果决定的。

fun main() {
    open class Shape

    class Rectangle: Shape()

    fun Shape.getName() = "Shape"

    fun Rectangle.getName() = "Rectangle"

    fun printClassName(s: Shape) {
        println(s.getName())
    }    

    printClassName(Rectangle())
}

// 输出:Shape

如果一个类的成员函数与扩展函数具有相同的接收者类型和形同的函数名,并且都适用该定的参数,这种情况总是取成员函数。

fun main() {
    class Example {
        fun printFunctionType() { println("Class method") }
    }

    fun Example.printFunctionType() { println("Extension function") }

    Example().printFunctionType()
}

// 输出:Class Method
  • 拓展声明为成员

声明为成员的扩展可以声明为open并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。

4. 数据类

主构造函数需要至少有一个参数。

componentN()函数按声明顺序对应与所有属性。

5. 密封类

声明:类名前添加sealed修饰符。

一个密封类是自身的抽象的,它不能直接实例化。

密封类用来表示受限的类继承结构:当一个值为有限几种类型,而不能有其他类型时。

使用密封类的关键好处:使用when表达式的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加else子句了。

sealed class SealedClass

class Bird(var name: String) : SealedClass()
class Fish(var name: String) : SealedClass()

fun getAnimalName (animal: SealedClass): String = when(animal) {
    is Bird -> "鸟类:${animal.name}"
    is Fish -> "鱼类:${animal.name}"
}

6. 泛型

泛型协变:取出对象(out)是安全的,传入对象(in)不可靠。

泛型逆变:传入对象(in)是安全的,取出对象(out)不可靠。

  • 声明处型变(declaration-site variance)

注:只能读取的对象为生产者(out),只能写入的对象为消费者(in)

标注Source的类型参数T来确保它从Source<T>中返回(生产),并从不被消费。使用out修饰符。

class User<out T> {
    val info: T
    
    constructor(info: T) {
        this.info = info
    }

    fun test(): T {
        println("执行test方法")
        return info
    }
}

当一个类 C 的类型参数 T 被声明为 out时,它就只能出现在 C 的成员的输出位置,但回报是 C<Base> 可以安全地作为 C<Derived>的超类。out修饰符被称为型变注解,也称为声明处型变

in使得一个类型参逆变:只能被消费而不能被生产。

如果泛型T只出现在该类的方法的返回值声明中,那么该类泛型形参即可使用out修饰T;如果泛型T只出现在该类的方法的形参声明中,那么该泛型形参即可使用in修饰T

  • 类型投影(使用处型变,type projections)
import java.lang.StringBuilder

fun main() {
    fun fill(dest: Array<in String>, value: String) {
        if (dest.isNotEmpty()) {
            dest[0] = value
        }
    }
    val arr1: Array<CharSequence> = arrayOf("a", "b", StringBuilder("cc"))
    fill(arr1, "kotlin")
    println(arr1.contentToString())

    var intArr: Array<in Int> = arrayOf(1, 2, 3)
    intArr[0] = 4
    println(intArr.contentToString())

    val numArr: Array<Number> = arrayOf(1, 2.1, 3.33333)
    intArr = numArr
    println(intArr.contentToString())

}

Array<out Any>相当于Java的泛型上限:Array<? extends Objcject>

Array<in Int>相当于Java的泛型下限:Array<? super Int>,因此这种泛型型变用于支持逆变,还可以保证向其中安全地添加元素。

  • 星投影

类似Java的原始数据类型。

如果类型被声明为Interface Function<in T, out U>,则:

Function<*, String>表示Function<in Nothing, String>

Function<Int, *>表示Function<Int, out Any?>

Function<*, *>表示Function<in Nothing, out Ant?>

  • 泛型函数

类型参数放在函数名称之前。

fun <T> singletonList(item: T): List<T> {  }

// 调用
val a1 = singletonList<Int>(1)
val a2 = singletonList(1)
  • 泛型约束

上界:冒号之后指定的类型是上界。

fun <T : Comparable<T>> sort(list: List<T>) {  }

默认的上界是Any?。尖括号中只能指定一个上界,如果同一类型参数需要多个上界,则需要一个单独的where子句。

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

7. 嵌套类与内部类

  • 嵌套类
class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
        val innerBar = bar  // 编译无法通过
    }
}

val demo = Outer.Nested().foo()  // ==2
  • 内部类

inner关键字的嵌套类能够访问外部类的成员。内部类带有一个对外部类对象的引用。

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

val demo = Outer().Inner().foo() // == 1
  • 匿名内部类

使用对象表达式创建匿名内部类实例。

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

8. 对象

  • 对象表达式

如果只需要一个对象,采用如下方式:

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}
  • 伴生对象

类内部的对象声明可以用companion关键字标记。

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

伴生对象的成员可通过只使用类名作为限定符来调用。

val instance = MyClass.create() 

省略对象的名称将默认使用名称Companion

class MyClass {
    companion object { }
}

val x = MyClass.Companion

其自身的类名可用作该类的伴生对象的引用。

class MyClass1 {
    companion object Named { }
}

val x = MyClass1  // MyClass1$Named@816f27d

class MyClass2 {
    companion object { }
}

val y = MyClass2  // MyClass2$Companion@87aac27

9. 类型别名

为现有类型提供替代名称。

class A {
    inner class Inner {
        fun inner() {
            println("A's inner")
        }
    }
}

typealias AInner = A.Inner

fun main() {
    val aInner: AInner = A().Inner()
    aInner.inner()  // A's inner
}

10. 内联类

内联类必须含有唯一的一个属性在主构造函数中初始化。

内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数。

inline class Name(val s: String) {
    val length: Int
        get() = s.length

    fun greet() {
        println("Hello, $s")
    }
}

注:内联类不能含有init代码块,不能含有幕后字段。

内联类只能含有简单的计算属性。

内联类允许继承接口,不能继承其他类。

interface Printable {
    fun prettyPrint(): String
}

inline class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}
  • 表示方式

只要将内联类用作另一种类型,它就会被装箱。

interface I

inline class Foo(val i: Int) : I
class Bar(val i: Int): I

fun <T> id(x: T): T = x

fun main() {
    val f = Foo(10)
    val foo = id(f)  // Foo(i=10)
    val b = Bar(20)
    val bar = id(b)  // Bar@1218025c
}
  • 内联类与类型别名

区别:类型别名与其基础类型是赋值兼容的,内联类不满足。内联类引入了一个真实的新类型。

typealias StringAlias = String
inline class StringInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptStringAlias(n: StringAlias) {}
fun acceptStringInlineClass(p: StringInlineClass) {}

fun main() {
    val stringAlias: StringAlias = ""
    val stringInlineClass: StringInlineClass = StringInlineClass("")
    val string = ""

    acceptString(stringAlias) // 正确: 传递别名类型的实参替代函数中基础类型的形参
    acceptString(stringInlineClass) // 错误: 不能传递内联类的实参替代函数中基础类型的形参

    acceptStringAlias(string) // 正确: 传递基础类型的实参替代函数中别名类型的形参
    acceptStringInlineClass(string) // 错误: 不能传递基础类型的实参替代函数中内联类类型的形参
}

11. 委托与委托属性

  • 标准委托

(1)延迟属性 Lazy

by lazy只能作用于val关键字标注的属性。当属性用到的时候才初始化lazy{}中的内容,再次调用属性的时候只会得到结果,不再执行lazy{}的运行过程。

val lazyValue: String by lazy {
    println("computed")
    "hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

// 输出
// computed
// hello
// hello

(2)可观察属性 Observable

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"  // no name -> first
    user.name = "second" // first -> second
}

Delegates.observable接受两个参数:初始值与修改时处理程序(handler)。每次给属性赋值会执行该处理程序。它有三个参数:被赋值的属性、旧值与新值。

  • 将属性储存在映射映射中

这种情况下,可以使用映射实例自身作为委托来实现委托属性。

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

fun main() {
    val user = User(mapOf(
        "name" to "John",
        "age"  to 25
    ))
    println(user.name) // John
    println(user.age)  // 25
}
  • 属性委托要求

(1)val属性,必须提供getValue()函数,该函数具有如下参数:

thisRef与属性所有者类型(扩展属性的被扩展类型)相同或者是其超类型。

property必须是类型KProperty<*>或其超类型。

getValue()必须返回与属性相同的类型或其子类型。

(2)var属性,必须额外提供一个setValue()函数,该函数具有如下参数:

thisRef与属性所有者类型(扩展属性的被扩展类型)相同或者是其超类型。

property必须是类型KProperty<*>或其超类型。

value必须与属性类型相同或者是其超类型。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342