第四章 kotlin 类型

120 阅读6分钟

4.1、类和接口

类的定义

1、默认是public。

2、如果类中没有内容,可以将{ } 省略。

3、类中定义属性,必须要初始化。

4、类中定义构造函数,使用constructor关键字。

5、构造器分为构造器和构造器,要求其它所有的构造器都必须调用它。

构造函数的几种形式

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        val test5 = Test5(1)


    }
}

// 只有副构造器
class Test {
    var x: Int = 0

    constructor(x: Int) {
        this.x = x
    }
}

// 包含主构造器和副构造器
class Test2 constructor(x: Int) {
    var x: Int = x
    var y: Int = 0

    constructor(x: Int, y: Int) {
        this.x = x
        this.y = y
    }
}

// 只有主构造器
class Test3 constructor(x: Int) {
    // 赋值
    var x: Int = x
}

// 将主构造器进行省略
class Test4(x: Int) {
    // 赋值
    var x: Int = x
}

// 继续简化:如果在构造器中增加了var/val,那么相当于上面的构造函数
class Test5(var x: Int) {
}

类的初始化(VS Java)

不需要new关键字

接口定义

interface SimpleInter {
    fun test()
}

接口的实现

// 使用:表示实现接口
class Test : SimpleInter {
    override fun test() {
    }
}
interface SimpleInter {
    fun test()
}

接口中增加属性并且实现

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        val test = Test(0)
        test.param = 4
        Log.e("cdx", test.param.toString())
    }
}

// 接口中定义了参数,这个参数不需要初始化
interface TestInterface {
    var param: Int
    fun test()
}

// 通过构造函数传递进来一个参数
class Test(var x: Int) : TestInterface {

    // 必须要重写这个参数
    override var param: Int
        // 获取参数
        get() {
            return x
        }
        // 设置参数
        set(value) {
            this.x = value
        }

    override fun test() {
    }
    
}

抽象类的定义

abstract class AbsTest {
    // 表示抽象方法
    abstract fun test1()

    // 必须加上open方法,才能告诉编译器,可以被复写
    open fun test2() {}

    // 默认是不可复写
    fun test3() {
    }
}

抽闲类的实现

// 接口继承:必须要加上() 这个和接口的实现不一样,接口是不需要加()
class Test : AbsTest() {
    override fun test1() {
    }

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

继承一个普通的类要求

继承一个普通的类A,这个A类必须要是open修饰的。

一个类的方法不能被重写

一个类的方法不能被复写,这个方法增加final。

// 要想能够继承一个类,这个类必须是open的
class Test2 : Test() {
    // 如果复写一个final修饰的方法,此时将会出现编译错误
//    override fun test2() {
//        super.test2()
//    }
}

// 接口继承:必须要加上() 这个和接口的实现不一样,接口是不需要加()
open class Test : AbsTest() {

    override fun test1() {
    }

    final override fun test2() {
        super.test2()
    }
}

abstract class AbsTest {
    // 表示抽象方法
    abstract fun test1()

    // 必须加上open方法,才能告诉编译器,可以被复写
    open fun test2() {}

    // 默认是不可复写
    fun test3() {
    }

}

field使用

field定义

      在get或者set方法中,表示这个属性

field使用

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        val person = Person(25, "张三")
        person.age = 18
        person.name = "小红"
        Log.e("cdx", person.toString())
    }
}

class Person(age: Int, name: String) {
    var age: Int = age
        get() {
            // field就是指向age属性
            return field
        }
        set(value) {
            field = value
        }
    var name: String = name

    override fun toString(): String {
        return "$age,$name"
    }
}

属性的引用 + 给属性的引用赋值值

属性的引用也是**::**来表示,和函数的引用是一样的。

	    // 这个数属性的引用,已经绑定了Receiver
        val property = person::age;
        property.set(1)
		// 这个属性的引用,没有绑定Receiver,所以需要在设置的时候,传递对象。
        val property2 = Person::age
        property2.set(person, 2)
        Log.e("cdx", (Person::age).toString())

4.2、扩展方法

定义:

不改变类的源码的情况下,为类增加自定义的方法。

如何实现:

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        Log.e("cdx", "abc".times(4))
    }

    fun String.times(times: Int): String {
        var builder = StringBuilder()
        for (i in 0 until times) {
            // 这里的this只得调用者本身
            builder.append(this)
        }
        return builder.toString()
    }
    
}

4.3、空类型安全

空类型访问属性

        var str: String? = null
        // 使用?.来安全访问
		val length = str?.length;//null
        Log.e("cdx", length.toString())//null

强制转换为不可空类型

通过两个!!将可空类型变为不可空类型

        // 表示是一个空类型
        var str: String? = "Hello"
        // 通过两个!!强转化成不为空类型
        val str1 = str!!;

        Log.e("cdx", str1.length.toString())

elvis运算符

        // 表示是一个空类型
        var str: String? = null

        // elvis运算符,如果str?.length 返回null的话,那么此时返回0
        val length = str?.length ?: 0
        Log.e("cdx", length.toString()) //0

空类型的继承关系

小范围(String)的可以给大范围(String?) 赋值,但是大范围的不能给小范围赋值。

        // 不为空
        var x: String = "a"
        // 可以为空(范围更大)
        var y: String? = "b"

        // 大范围的赋值给小范围,出现编译错误
        //x = y
        // 小范围的赋值给大范围,没有问题
        y = x

平台类型

定义

1、在java中声明的类型,在kotlin里面会被称为平台类型

2、这种类型的空检查会放宽

例子

首先定义一个person类,这个是Java的类。

package com.example.kotlindemo;

public class Person {

    public String name;

    public String getTitle(){
        return null;
    }
}

title的类型居然是String!

        var person = Person()
        // 去使用title的时候,本质上使用的是Person的getTitle属性
        val title = person.title
        // 编译器不知道title是否是可空类型,所以要进行非空判断
        var length = title?.length

4.4、智能类型转换

kotlin的类型转换

kotlin不需要强制转换为子类,就可以调用子类的属性。(这个和Java不太一样)

package com.example.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

		// 父类的引用指向子类的对象
        var person = Student();
        Log.e("cdx", "xxx" + person.name)
        if (person is Student) {
            // 不需要进行类型转换,自动调用子类的属性,下面的写法是精简的写法
            Log.e("cdx", (person as Student).name)
            Log.e("cdx", person.name)
        }
    }
}

interface Person {
    fun say()
}

class Student : Person {
    val name: String = "xh"
    override fun say() {

    }
}

作用范围

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        // 作用范围
        var value: String? = null
        // 这个是不行的,因为这个是可空的类型
        // Log.e("cdx", value.length.toString());
        if (value != null) {
            // 在括号里面,由于有非空判断,所以编译器很智能的将value的类型变为了String类型,非空的类型。
            Log.e("cdx", value.length.toString());
        }
        // 在这个地方,value的类型又变为了String可为空的类型
        Log.e("cdx", value?.length.toString());
    }

}

不支持智能转换的情况

class MainActivity2 : AppCompatActivity() {

    var tag: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        // 这个地方理论上有非空判断,为什么不能有作用范围的概念呢?
        // 因为tag是一个全局的字段,虽然判断不为空,
但是在判断为不为空的时候,
其它的线程可能将他的值修改为空,所以智能转换就不生效了
        if (tag != null) {
            // Log.e("cdx",tag.length.toString())
            Log.e("cdx", tag?.length.toString())
        }
    }

}

类型的安全转换

as ?

// 安全转换:

// 如果是person是Student类型,那么就转换

// 如果不是Student类型,那么返回null

        // 父类的引用指向子类的对象
        var person = Student();
        // 安全转换:
        //   如果是person是Student类型,那么就转换
        //   如果不是Student类型,那么返回null
        Log.e("cdx", (person as? Student)?.name.toString())

建议

1、尽量使用val来申明不可变的引用,让程序的含义更加清晰确定。