Kotlin学习日记-类与对象

1.类

1.1 声明类

Kotlin 中使用关键字 class 声明类,类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的;

class Person { /*……*/ }

如果一个类没有类体,可以省略中括号

class Empty

1.2 构造函数

  1. 在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。
  2. 主构造函数是类头的一部分:它跟在类名与可选的类型参数后。
class Person constructor(firstName: String) { /*……*/ }

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。

class Person(firstName: String) { /*……*/ }

主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中。

在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)

    init {
        println("First initializer block that prints $name")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")
    }
    
    fun main() {
       InitOrderDemo("hello")
    }
}

Kotlin能够在构造器中声明类成员并且可以设置初始值,与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。

class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)

如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:

class Customer public @Inject constructor(name: String) { /*……*/ }

1.3 次构造函数

类也可以声明前缀有 constructor的次构造函数:
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:

class Person(val name: String) {
    val children: MutableList<Person> = mutableListOf()
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

需要注意的是,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行。

即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor $i")
    }
    
    fun main() {
        Constructors(1)
    }
}

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public,这点跟Java一样。
如果你不希望你的类有一个公有构造函数,那么声明一个带有非默认可见性的空的主构造函数:

class DontCreateMe private constructor () { /*……*/ }

在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值。

1.4 创建类的实例

创建一个类的实例,只需像普通函数一样调用构造函数:
Kotlin中并没有new关键字

val invoice = Invoice()

val customer = Customer("Joe Smith")

1.5 类成员

类可以包含:

  • 构造函数与初始化块
  • 函数
  • 属性
  • 嵌套类与内部类
  • 对象声明

1.6 继承

Kotlin的类可以进行继承,详情将会在下面的继承篇章中展开。

1.7 抽象类

类以及其中的某些或全部成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 并不需要用 open 标注抽象类或者函数。

abstract class Polygon {
    abstract fun draw()
}

class Rectangle : Polygon() {
    override fun draw() {
        // draw the rectangle
    }
}

可以用一个抽象成员覆盖一个非抽象的开放成员。

open class Polygon {
    open fun draw() {
        // some default polygon drawing method
    }
}

abstract class WildShape : Polygon() {
    // Classes that inherit WildShape need to provide their own
    // draw method instead of using the default on Polygon
    abstract override fun draw()
}

1.8 伴生对象

伴生对象更像一个静态区域块,一般的场景用在需声明静态变量与静态函数时:

class Person{
    companion {
         const val SEX_MELE=0
         
         fun getSexMele(){
             return SEX_MELE
         
    }
}

fun main(){
    println(Person.SEX_MELE)
    println(Person.getSexMele())
}

2.继承

2.1 继承

在 Kotlin 中所有类都有一个共同的超类 Any,对于没有超类型声明的类它是默认超类:

class Example // 从 Any 隐式继承

Any 有三个方法:equals()、 hashCode() 与 toString()。因此,为所有 Kotlin 类都定义了这些方法。

默认情况下,Kotlin 类是最终(final)的——它们不能被继承。 要使一个类可继承,请用 open 关键字标记它:

open class Base // 该类开放继承

如需继承,请在类头中把父类放到冒号之后:

open class Base(p: Int)

class Derived(p: Int) : Base(p)

2.2 覆盖方法

Kotlin 对于可覆盖的成员以及覆盖后的成员需要显式修饰符(override):

open class Shape {
    open fun draw() { /*……*/ }
    fun fill() { /*……*/ }
}

class Circle() : Shape() {
    override fun draw() { /*……*/ }
}

标记为 override 的成员本身是开放的,因此可以在子类中覆盖。如果你想禁止再次覆盖, 使用 final 关键字:

open class Rectangle() : Shape() {
    final override fun draw() { /*……*/ }
}

2.3 覆盖属性

属性与方法的覆盖机制相同。在超类中声明然后在子类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 get 方法的属性覆盖:

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount = 4
}

你也可以用一个 var 属性覆盖一个 val 属性,因为val属性本质上只声明了一个get方法,而将其覆盖为var只是在子类中额外声明一个set方法,但反之不行。

interface Shape {
    val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点

class Polygon : Shape {
    override var vertexCount: Int = 0  // 以后可以设置为任何数
}

2.4 继承初始化顺序

在构造子类的新实例的过程中,第一步完成其基类的初始化,这意味着它发生在子类的初始化逻辑运行之前。

open class Base(val name: String) {

    init { println("Initializing a base class") }

    open val size: Int = 
        name.length.also { println("Initializing size in the base class: $it") }
}

class Derived(
    name: String,
    val lastName: String,
) : Base(name.replaceFirstChar { it.uppercase() }.also { println("Argument for the base class: $it") }) {

    init { println("Initializing a derived class") }

    override val size: Int =
        (super.size + lastName.length).also { println("Initializing size in the derived class: $it") }
}

fun main() {
    println("Constructing the derived class(\"hello\", \"world\")")
    Derived("hello", "world")
}
/**日志输出:
     1.Constructing the derived class("hello", "world")
     2.Argument for the base class: Hello
     3.Initializing a base class
     4.Initializing size in the base class: 5
     5.Intializing a derived class
     6.Initializing size in the derived class: 10
 */

2.5 调用父类实现

子类中的代码可以使用 super 关键字调用其父类的函数与属性访问器的实现

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
    override fun draw() {
        super.draw()
        println("Filling the rectangle")
    }

    val fillColor: String get() = super.borderColor
}

在一个内部类中访问外部类的父类,可以使用由外部类名限定的 super 关键字来实现:super@Outer:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

class FilledRectangle: Rectangle() {
    override fun draw() {
        val filler = Filler()
        filler.drawAndFill()
    }

    inner class Filler {
        fun fill() { println("Filling") }
        fun drawAndFill() {
            super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
            fill()
            println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
        }
    }
}

fun main() {
    val fr = FilledRectangle()
        fr.draw()
}

2.6 覆盖规则

如果继承的接口与类中有相同的函数、属性,子类必须覆盖该函数、属性并为其提供自己的实现,来消除歧义。

可通过尖括号+类名来决定调用哪个具体实现,如super.fun()

open class Rectangle {
    open fun draw() { /* …… */ }
}

interface Polygon {
    fun draw() { /* …… */ } // 接口成员默认就是“open”的
}

class Square() : Rectangle(), Polygon {
    // 编译器要求覆盖 draw():
    override fun draw() {
        super<Rectangle>.draw() // 调用 Rectangle.draw()
        super<Polygon>.draw() // 调用 Polygon.draw()
    }
}

3.属性

3.1 声明属性

Kotlin 类中的属性既可以用关键字 var 声明为可变的, 也可以用关键字 val 声明为只读的。

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

3.2 Getter 与 Setter

声明一个属性的完整语法如下:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

可以看出跟Java不一样的是,属性自带get与set,还有初始器,但是这个语法组成在某些情况下是可以忽略的

  1. getter与setter是可选的
  2. initializer声明在构造函数时是可选的
  3. 属性类型在显示初始化器或getter中能推断出来时是可选的

val与var不同的点在于,val不允许设置setter

可以为属性定义自定义的访问器。如果定义了一个自定义的 getter,那么每次访问该属性时都会调用它 (这让可以实现计算出的属性)。以下是一个自定义 getter 的示例:

class Rectangle(val width: Int, val height: Int) {
    val area: Int // property type is optional since it can be inferred from the getter's return type
        get() = this.width * this.height
}

如果定义了一个自定义的 setter,那么每次给属性赋值时都会调用它, except its initialization. 一个自定义的 setter 如下所示:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // 解析字符串并赋值给其他属性
    }

3.3 幕后字段

在 Kotlin 中,字段仅作为属性的一部分在内存中保存其值时使用。如需要在访问器中访问字段中的值,需要用到field标识符.

var counter = 0 // 这个初始器直接为幕后字段赋值
    set(value) {
        if (value >= 0)
            field = value
            // counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
    }

3.4 编译器常量

如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量。 这种属性需要满足以下要求:

  • 必须位于顶层或者是 object 声明 或伴生对象的一个成员
  • 必须以 String 或原生类型值初始化
  • 不能有自定义 getter
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

3.5 延迟初始化属性与变量

一般情况下,属性声明为非空类型必须在构造函数中初始化,但是有某些场景下,这些属性并不是为空,只是延迟赋值而已,那么在这种场景下,我们可通过 lateinit 修饰符标记该属性:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()   
    }
}

该修饰符能用于类中声明的属性、顶层属性、局部变量的场景中,该属性或变量必须为非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit 属性会抛出一个异常。

3.6 检测一个lateinit var 是否已经出初始化

要检测一个 lateinit var 是否已经初始化过,请在该属性的引用上使用 .isInitialized:

if (foo::bar.isInitialized) {
    println(foo.bar)
}

4.接口

Kotlin 的接口可以既包含抽象方法的声明也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
使用关键字 interface 来定义接口:

interface MyInterface {
    fun bar()
    fun foo() {
      // 可选的方法体
    }
}

4.1 实现接口

一个类或者对象可以实现一个或多个接口:

class Child : MyInterface {
    override fun bar() {
        // 方法体
    }
}

4.2 接口中的属性

可以在接口中定义属性,在接口中声明的属性要么是抽象的,要么提供访问器实现。
在接口声明的属性不能有幕后字段,因此接口中声明的访问器不能引用它们:

interface MyInterface {
    val prop: Int // 抽象的

    val propertyWithImplementation: String
        get() = "foo"

    fun foo() {
        print(prop)
    }
}

class Child : MyInterface {
    override val prop: Int = 29
}

4.3 接口继承

一个接口可以继承其他接口,意味着既能提供基类型成员的实现也能声明新的函数与属性。

interface Named {
    val name: String
}

interface Person : Named {
    val firstName: String
    val lastName: String

    override val name: String get() = "$firstName $lastName"
}

data class Employee(
    // 不必实现“name”
    override val firstName: String,
    override val lastName: String,
    val position: Position
) : Person

5.函数式接口(SAM)

只有一个抽象方法的接口称为函数式接口或 单一抽象方法(SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
可以用fun修饰符在Kotlin中声明一个函数式接口。

fun interface KRunnable {
   fun invoke()
}

5.1 SAM 转换

先看看不用SAM转换的情况下写一个接口实例

fun interface IntPredicate {
   fun accept(i: Int): Boolean
}

// 创建一个类的实例
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0
   }
}

看到这里你肯定有这个想法,厚礼蟹,这也太麻烦了吧?
但是不要慌,Kotlin已经为我们简化好了写法,只要满足SAM的接口,我们都可以简化成Lambda,看看效果。

val isEven = IntPredicate { it % 2 == 0 }

6.可见性修饰符

类、对象、接口、构造函数、方法与属性及其 setter 都可以有可见性修饰符。 getter 总是与属性有着相同的可见性。
在 Kotlin 中有这四个可见性修饰符:private、 protected、 internal 和 public。 默认可见性是 public。

6.1 包

函数、属性和类、对象和接口可以直接在包内的顶层声明:

  • 如果你不使用任何可见性修饰符,默认为 public,这意味着你的声明将随处可见。
  • 如果你声明为 private,它只会在声明它的文件内可见。
  • 如果你声明为 internal,它会在相同模块内可见。
  • protected 修饰符不适用于顶层声明。
package foo
private fun foo() { …… } // 在 example.kt 内可见

public var bar: Int = 5 // 该属性随处可见
    private set         // setter 只在 example.kt 内可见

internal val baz = 6    // 相同模块内可见

6.2 类成员

对于类内部声明的成员:

  • private 意味着只该成员在这个类内部(包含其所有成员)可见;
  • protected 意味着该成员具有与 private 一样的可见性,但也在子类中可见。
  • internal 意味着能见到类声明的本模块内的任何客户端都可见其 internal 成员。
  • public 意味着能见到类声明的任何客户端都可见其 public 成员。

如果你覆盖一个 protected 或 internal 成员并且没有显式指定其可见性,该成员还会具有与原始成员相同的可见性。

open class Outer {
    private val a = 1
    protected open val b = 2
    internal open val c = 3
    val d = 4  // 默认 public

    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可见
    // b、c、d 可见
    // Nested 和 e 可见

    override val b = 5   // “b”为 protected
    override val c = 7   // 'c' is internal
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可见
    // o.c 和 o.d 可见(相同模块)
    // Outer.Nested 不可见,Nested::e 也不可见
}

6.3 构造函数

使用以下语法来指定一个类的的主构造函数的可见性:

class C private constructor(a: Int) { …… }

6.4 局部声明

局部变量、函数和类不能有可见性修饰符。

6.5 模块

可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件,例如:

  • 一个 IntelliJ IDEA 模块
  • 一个 Maven 项目
  • 一个 Gradle 源代码集(例外是 test 源代码集可以访问 main 的 internal 声明)
  • 一次 Ant 任务执行所编译的一套文件

7.扩展

有没有一个痛,是来源你想改别人源码,但是又因为各种原因改不动?
Kotlin的扩展就为你打开了这个大门,没错,在任意类拓展函数 or 属性,我个人认为最鸡儿棒的功能!

7.1 扩展函数

声明一个扩展函数需用一个接收者类型也就是被扩展的类型来作为他的前缀。
下面代码为 MutableList 添加一个swap 函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
}

这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,可以对任意 MutableList 调用该函数了:

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值

并且可以将泛型泛化:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
}

这样就适用所有MutableList类型了~

7.2 扩展是静态解析的

扩展不能真正的修改他们所扩展的类。通过定义一个扩展,并没有在一个类中插入新成员, 只不过是可以通过该类型的变量用点表达式去调用这个新函数

扩展函数是静态分发的,调用的扩展函数是由调用所在的类型所决定的

fun main() {
//sampleStart
    open class Shape
    class Rectangle: Shape()

    fun Shape.getName() = "Shape"
    fun Rectangle.getName() = "Rectangle"

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

    printClassName(Rectangle())
//sampleEnd
}

这个例子会输出 Shape,因为调用的扩展函数只取决于参数 s 的声明类型,该类型是 Shape 类。

如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字,并且都适用给定的参数,这种情况总是取成员函数。 例如:

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

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

    Example().printFunctionType()
//sampleEnd
}

这段代码输出 Class method。

7.3 可空接收者

注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null。

这样,就可以在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}

7.4 扩展属性

与扩展函数类似,Kotlin 支持扩展属性:

val <T> List<T>.lastIndex: Int
    get() = size - 1

Tips: 由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getter/setter 定义。

例如:

例如:

val House.number = 1 // 错误:扩展属性不能有初始化器

7.5 伴生对象的扩展

如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数与属性。就像伴生对象的常规成员一样, 可以只使用类名作为限定符来调用伴生对象的扩展成员:

class MyClass {
    companion object { }  // 将被称为 "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
    MyClass.printCompanion()
}

7.6 扩展的作用域

大多数情况都在顶层定义扩展——直接在包里:

package org.example.declarations

fun List<String>.getLongestString() { /*……*/}

7.7 扩展声明为成员

可以在一个类内部为另一个类声明扩展。在这样的扩展内部,有多个隐式接收者—— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为分发接收者,扩展方法调用所在的接收者类型的实例称为扩展接收者。

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }

    fun Host.printConnectionString() {
         printHostname()   // 调用 Host.printHostname()
        print(":")
         printPort()   // 调用 Connection.printPort()
    }

    fun connect() {
         /*……*/
         host.printConnectionString()   // 调用扩展函数
    }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()
    //Host("kotl.in").printConnectionString()  // 错误,该扩展函数在 Connection 外不可用
}
fun List<String>.getLongestString() { /*……*/}

对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this 语法。

class Connection {
    fun Host.getConnectionString() {
        toString()         // 调用 Host.toString()
        this@Connection.toString()  // 调用 Connection.toString()
    }
}

8.数据类

创建一些只保存数据的类是件寻常的事。 在这些类中,一些标准功能以及一些工具函数往往是由数据机械推导而来的。在 Kotlin 中,这叫做 数据类 并以 data 标记:

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

编译器自动从主构造函数中声明的所有属性导出以下成员:

  • equals()/hashCode()
  • toString() 格式是 “User(name=John, age=42)”
  • componentN() 函数 按声明顺序对应于所有属性。
  • copy() 函数(见下文)

为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:

  • 主构造函数需要至少有一个参数。
  • 主构造函数的所有参数需要标记为 val 或 var。
  • 数据类不能是抽象、开放、密封或者内部的。

此外,数据类成员的生成遵循关于成员继承的这些规则:

  • 如果在数据类体中有显式实现 equals()、 hashCode() 或者 toString(),或者这些函数在父类中有 final 实现,那么不会生成这些函数,而会使用现有函数。
  • 如果超类型具有 open 的 componentN() 函数并且返回兼容的类型, 那么会为数据类生成相应的函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final 而导致无法覆盖,那么会报错。
  • 不允许为 componentN() 以及 copy() 函数提供显式实现。

8.1 在类体中声明的属性

对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性。 如需在生成的实现中排除一个属性,请将其声明在类体中:

data class Person(val name: String) {
    var age: Int = 0
}

在 toString()、 equals()、 hashCode() 以及 copy() 的实现中只会用到 name 属性, 并且只有一个 component 函数 component1()。虽然两个 Person 对象可以有不同的年龄, 但它们会视为相等。

data class Person(val name: String) {
    var age: Int = 0
}
fun main() {
//sampleStart
    val person1 = Person("John")
    val person2 = Person("John")
    person1.age = 10
    person2.age = 20
//sampleEnd
    println("person1 == person2: ${person1 == person2}")
    println("person1 with age ${person1.age}: ${person1}")
    println("person2 with age ${person2.age}: ${person2}")
}

8.2 复制

使用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)

8.3 数据类与解构声明

为数据类生成的 component 函数 使它们可在解构声明中使用:

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

10.泛型

与Java一样,泛型也有类型参数

class Box<T>(t: T) {
    var value = t
}

val box: Box<Int> = Box<Int>(1)

但是如果类型参数可以推断出来,例如从构造函数的参数或者从其他途径, 就可以省略类型参数:

val box = Box(1) // 1 具有类型 Int,所以编译器推算出它是 Box<Int>

10.1 星投影

某些场景下,你想声明一个类,但是对他的类型并不在意,这个时候就可以使用*号占位

MutableList<*> 

这种情况下,*代表Nothing类型,这种写法往往在你不在意泛型是什么具体类型的场景下用

10.2 泛型函数

不仅类可以有类型参数。函数也可以有。类型参数要放在函数名称之前:

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

10.3 泛型约束

能够替换给定类型参数的所有可能类型的集合可以由泛型约束限制。

最常见的约束类型是上界,与 Java 的 extends 关键字对应:

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

11.嵌套类

11.1 嵌套类

类可以嵌套在其他类中:

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

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

11.2 内部类

标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用:

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

val demo = Outer().Inner().foo() // == 1

11.3 匿名内部类

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

window.addMouseListener(object : MouseAdapter() {

    override fun mouseClicked(e: MouseEvent) { …… }

    override fun mouseEntered(e: MouseEvent) { …… }
})

tips:对于 JVM 平台, 如果对象是函数式 Java 接口(即具有单个抽象方法的 Java 接口)的实例,可以使用带接口类型前缀的 lambda 表达式创建它:

14.对象表达式与对象声明

14.1 对象表达式

14.1.1 创建匿名对象

如果不继承任何父类,我们可以直接通过==object{}==创建匿名类

fun main() {
//sampleStart
    val helloWorld = object {
        val hello = "Hello"
        val world = "World"
        // object expressions extend Any, so `override` is required on `toString()`
        override fun toString() = "$hello $world"
    }
//sampleEnd
    print(helloWorld)
}
14.1.2 继承父类的匿名对象

如果从某个父类继承创建匿名对象,则通过==object:Parent{}==来创建匿名对象

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*……*/ }

    override fun mouseEntered(e: MouseEvent) { /*……*/ }
})
14.1.3 从匿名对象访问变量

匿名对象中可以访问创建它的对象的作用域:

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

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

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ……
}

14.2 对象声明

单例模式在一些场景中很有用, 而 Kotlin 使单例声明变得很容易:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ……
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ……
}

对象声明的初始化过程是线程安全的并且是懒汉式

14.3 伴生对象

一般使用的场景是声明静态类成员与静态函数时使用。
类内部的对象声明可以用 companion 关键字标记:

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

伴生对象的成员可通过只是用类名来调用

val instance = MyClass.create()

在 JVM 平台,如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段。

14.4 表达式对象和对象声明之间的形态

对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行(及初始化)的。
  • 对象声明是在第一次被访问到时延迟初始化的。
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

17.类型别名

有没有烦恼过某些类型太过于长,比如:

val data=MutableLiveData<MutableList<Triple<Any, Any, Any>>>?

那么Kotlin给予了这些究极长的类型提供别名的机会,优化如下

typealias CustomType = MutableLiveData<MutableList<Triple<Any, Any, Any>>>?

//那么声明类型的时候就可以简化如下:
val data : CustomType? =null

确实舒服

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值