Kotiln基础语法总结(二)

一、主构造函数


我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名

class Player(
        _name: String,
        _age: Int,
        _isNormal: Boolean
){

    var name = _name
        get() = field.capitalize()
        private set(value) {
            field = value.trim()
        }

    var age = _age
    var isNumber = _isNormal
}


1.1、主构造函数里定义属性

Kotlin允许你不使用临时变量赋值,而是直接用一个定义同时指定参数和类属性,通常,我们更喜欢用这种方式定义类属性,因为他会减少重复代码。

class Player(
        _name: String,
        var age: Int,
        val isNormal: Boolean
){
    //属性不需要赋值
    var name = _name
        get() = field.capitalize()
        private set(value) {
            field = value.trim()
        }

    //次构造函数
    constructor(name: String): this(name, 100, false){
        this.name = name.toUpperCase()
    }
    
}	

1.2、默认参数

定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值。

1.3、初始化块

初始化快可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化代码会在构造类实例时执行。

init{

}

1.4、初始化顺序

1.主构造函数里声明的属性
2.类级别的属性赋值
3.init初始化快里的属性赋值和函数调用
4.次构造函数里的属性赋值和函数调用

在这里插入图片描述

1.5、延迟初始化

class Player{

    lateinit var equipment: String

    fun ready(){
        equipment = "sharp knife"
    }

    fun battle(){
        //检查是否初始化了
        if(::equipment.isInitialized) println(equipment)
    }

}

1.6、惰性初始化

延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这叫做惰性初始化。

class Player(_name: String){

    var name = _name

    val config by lazy { loadConfig() }

    private fun loadConfig(): String{
        println("loading")
        return "xxx"
    }
}

二、初始化陷阱


2.1、陷阱一

在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化

class Player(){
    //这个必须放在初始化代码之前
    val blood = 100
    init {
        val bloodBonus = blood.times(4)
    }
}

2.2、陷阱二

这段代码编译没有问题,因为编译器看到name已经在init块里初始化了,但代码一运行,就会抛出空指针异常,因为name属性还没赋值,firstLetter函数就应用它了。

class Player(){
    var name: String
    private fun firstLetter() = name[0]

    init {
        println(firstLetter())
        name = "jack"  //jack放在这里报空指针异常
    }
}

2.3、陷阱三

在用initPlayerName函数初始化时,name属性还未完成初始化

class Player(_name: String){

	//这里赋值的时候,name还未初始化
    var playerName: String = initPlayerName()

    val name: String = _name
    private fun initPlayerName() = name

}

三、继承


类默认都是封闭的,要让某个类开放继承,必须使用open关键字修饰它

open class Product(val name: String){
    fun description() = "Product:$name"

    open fun load() = "Nothing..."

}

class LuxuryProduct: Product("Luxury"){

    override fun load() = "LuxuryProduct loading"

}

fun main(){
    val p = LuxuryProduct()
    println(p is Product) //true
    println(p is LuxuryProduct) //true
    //无须在代码里显示指定,每一个类都会继承一个共同的叫作Any的超类
    println(p is Any) //true
}

3.1、类型转换

编译器允许不经类型转换直接使用

fun sale(p: Product){
    println(p.load())
}

fun main(){
    val p = LuxuryProduct()
    sale(p as Product)
}

四、object


使用object关键字,你可以定义一个只能产生一个实例的类-单例
使用object关键字有三种方式
1.对象声明
2.对象表达式
3.伴生对象

4.1、单例

object ApplicationConfig{

    init{
        println("loadingConfig..")
    }

    fun setSomething(){
        println("set something")
    }

}

4.2、对象表达式(匿名内部类)

open class Player{
    open fun load() = "Nothing..."
}

fun main(){
    //匿名内部类
    val p = object : Player(){
        override fun load() = "anoymous class load..."
    }

    println(p.load())
}

4.3、伴生对象

如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用companion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。

open class ConfigMap{
	//只有初始化ConfigMap类或调用load函数时,伴生对象的内容才会载入。
	//而且无论实例化ConfigMap类多少次,这个伴生对象始终只有一个实例存在
    companion object{
        private const val PATH = "xxx"
		
        fun load() = File(PATH).readBytes()

    }

}

五、嵌套类


如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类

class Player{
    private class Equipment(var name: String){
        fun show() = println("Equipment:$name")
    }

    fun battle(){
        Equipment("sharp knife").show()
    }
}

六、数据类


数据类,是专门设计用来存储数据的类
数据类提供了toString的个性化实现
==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hashCode的个性化实现

//会自动重写toString方法
data class Coordinate(var x:Int, var y:Int){
    var isInBounds = x > 0 && y > 0
}

6.1、copy

除了重写Any类的部分函数,提供更好用的默认实现外,数据类还提供了一个函数,它可以用来方便地复制一个对象。

使用数据类的条件:
正是因为上述这些特性,你才倾向于用数据类来表示存储数据的简单对象,对于那些经常需要比较、复制或打印自身内容的类,数据类尤其适合它们,然而,一个类要成为数据类,也要符合一定条件。总结下来,主要有三个方面:
1.数据类必须有至少带一个参数的主构造函数
2.数据类主构造函数的参数必须是val或var
3.数据类不能使用abstract、open、sealed和inner修饰符

七、解构声明


解构声明的后台实现就是component1、compent2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数。

class PlayerScore(val experience: String, val level: Int){
    operator fun component1() = experience
    operator fun component2() = level
}

fun main() {
    val (x, y) = PlayerScore("e", 10)
}

八、枚举


枚举类,用来定义常量集合的一种特殊类,也可以定义函数

data class Coordinate(var x:Int, var y:Int)
enum class Direction(val coordinate: Coordinate){
    EAST(Coordinate(10, 10)),
    WEST(Coordinate(20, 20)),
    SOUTH(Coordinate(30, 30)),
    NORTH(Coordinate(40, 40));

    fun updateCoordinate(playerCoordinate: Coordinate)
        = Coordinate(coordinate.x + playerCoordinate.x, coordinate.y + playerCoordinate.y)

	//当两个对象equals为true时,hashCode值也应该相等
}

九、运算符重载


如果将内置运算符应用在自定义类身上,你必须重写运算符函数,告诉编译器该如何操作自定义类。

class Coordinate(var x:Int, var y:Int){
    var isInBounds = x > 0 && y > 0;

    operator fun plus(other: Coordinate)
        = Coordinate(x + other.x, y + other.y)

}

常见操作符
在这里插入图片描述

十、代数数据类型


可以用来表示一组子类型的闭集,枚举类就是一种简单的ADT

enum class LincenseStatus{
    UNQUALIFIED,
    LEARNING,
    QUALIFIED
}

class Diriver(var status: LincenseStatus){
    fun checkLicense(): String{
        return when(status){
            LincenseStatus.UNQUALIFIED -> "没资格"
            LincenseStatus.LEARNING -> "在学"
            LincenseStatus.QUALIFIED -> "有资格"
        }
    }
}

十一、密封类

对于更复杂的ADT,你可以使用Kotlin的密封类(sealed class)来实现更复杂的定义,密封类可以用来定义一个类似枚举类的ADT,但你可以更灵活的控制某个子类型。
密封类可以用若干子类,要继承密封类,这些子类必须和它定义在同一个文件里

sealed class LicenseStatus{
    object Unqualified: LicenseStatus()
    object Learning: LicenseStatus()
    class Qualified(val licenseId: String): LicenseStatus()
}

class Diriver(var status: LicenseStatus){
    fun checkLicense(): String{
        return when(status){
            is LicenseStatus.Unqualified -> "没资格"
            is LicenseStatus.Learning -> "在学"
            is LicenseStatus.Qualified -> "有资格,驾驶证编号:${(this.status as LicenseStatus.Qualified).licenseId}"
        }
    }
}

十二、接口


Kotlin规定所有的接口属性和函数实现都要使用override关键字,接口中定义的函数并不需要open关键字修饰,他们默认就是open的。

interface Movable{

    //只要你愿意,你可以在接口里提供默认属性的getter方法和函数实现
    val maxSpeed: Int
        get() = (1..400).shuffled().last()
    var wheels: Int

    fun move(movable: Movable): String

}

class Car(_name: String,
          override var wheels: Int = 4
          ): Movable{

    override var maxSpeed: Int
        get() = super.maxSpeed
        set(value) {
        }

    override fun move(movable: Movable): String {
        return ""
    }

}

十三、抽象类


要定义一个抽象类,你需要在定义之前加上abstract关键字,除了具体的函数实现,抽象类也可以包含抽象函数–只有定义,没有函数实现。

abstract class Gun(val range: Int){

    abstract fun pullTrigger(): String

}

class AK47(val price: Int): Gun(500){

    override fun pullTrigger(): String {
        TODO("Not yet implemented")
    }

}

十四、泛型


class MagicBox<T>(item: T){
    private var subject: T = item
}

class Boy(val name: String, val age: Int)

class Dog(val weight: Int)

fun main(){
    MagicBox<Boy>(Boy("Jack", 20))
    MagicBox<Dog>(Dog(20))
}

注意:
泛型参数通常用字母T(代表英文type)表示,当然,想用其他字母,甚至是英文单词都是可以的。不过,其他支持泛型的语言都是用这个约定俗成的T,所以建议你继续用它,这样写出的代码别人更容易理解。

14.1、泛型函数

泛型参数也可以用于函数
定义一个函数用于获取元素,当且仅当MagicBox可用时,才能获取元素。

class MagicBox<T>(item: T){
    var available = false
    private var subject: T = item

    fun fetch(): T?{
        return subject.takeIf { available }
    }
}

class Boy(val name: String, val age: Int)

fun main(){
    val magicBox = MagicBox<Boy>(Boy("Jack", 20))
	magicBox.available = true
    magicBox.fetch()?.run {
        println("you find $name")
    }
}

14.2、多泛型参数

泛型函数或泛型类也可以有多个泛型参数。

class MagicBox<T>(item: T){
    var available = false
    private var subject: T = item

    fun fetch(): T?{
        return subject.takeIf { available }
    }

    //业务,元素进行修改
    fun <R> fetch(subjectModFunction: (T) -> R): R?{
        return subjectModFunction(subject).takeIf { available }
    }
}

class Boy(val name: String, val age: Int)

class Man(val name: String, val age: Int)


fun main(){
    val magicBox = MagicBox<Boy>(Boy("Jack", 20))

    magicBox.fetch()?.run {
        println("you find $name")
    }

    magicBox.fetch{
        Man(it.name, it.age.plus(15))
    }
}

14.3、泛型类型约束

如果要确保MagicBox里面只能装指定类型的物品,如Human类型,怎么办?

class MagicBox<T: Human>(item: T){
    var available = false
    private var subject: T = item

    fun fetch(): T?{
        return subject.takeIf { available }
    }

    //业务,元素进行修改
    fun <R> fetch(subjectModFunction: (T) -> R): R?{
        return subjectModFunction(subject).takeIf { available }
    }
}

open class Human(val age: Int)

class Boy(val name: String, age: Int): Human(age)

class Man(val name: String, age: Int): Human(age)

14.4、vararg关键字与get函数

MagicBox能存放任何类型的Human实例,但一次只能放一个,如果需要放多个实例了?

class MagicBox<T: Human>(vararg item: T){
    var available = false
    private var subject: Array<out T> = item

    fun fetch(index: Int): T?{
        return subject[index].takeIf { available }
    }

    //业务,元素进行修改
    fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R?{
        return subjectModFunction(subject[index]).takeIf { available }
    }
}

open class Human(val age: Int)

class Boy(val name: String, age: Int): Human(age)

class Man(val name: String, age: Int): Human(age)


fun main(){
    val magicBox = MagicBox<Boy>(
            Boy("Jack", 20),
            Boy("Jacky", 16)
    )

    magicBox.available = true

    magicBox.fetch(0){
        Man(it.name, it.age.plus(15))
    }
}

14.5、[]操作符取值

想要通过[]操作符取值,可以重载运算符get函数

class MagicBox<T: Human>(vararg item: T){
    var available = false
    private var subject: Array<out T> = item

    fun fetch(index: Int): T?{
        return subject[index].takeIf { available }
    }

    //业务,元素进行修改
    fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R?{
        return subjectModFunction(subject[index]).takeIf { available }
    }

    operator fun get(index: Int): T? = subject[index]?.takeIf { available }
}

open class Human(val age: Int)

class Boy(val name: String, age: Int): Human(age)

class Man(val name: String, age: Int): Human(age)


fun main(){
    val magicBox = MagicBox<Boy>(
            Boy("Jack", 20),
            Boy("Jacky", 16)
    )

    magicBox.available = true

    magicBox.fetch(0){
        Man(it.name, it.age.plus(15))
    }

	//注意这里,[]取值
    magicBox[0]
}

十五、outininvariant 不变


out(协变),如果泛型类只将泛型类型作为函数的返回(输出),那么使用out,可以称之为生产类/接口,因为它主要是用来生产(produce)指定的泛型对象

in(逆变),如果泛型类只将泛型类型作为函数的入参(输入),那么使用in,可以称之为消费者类/接口,因为它主要是用来消费(consume)指定的泛型对象。

如果泛型类既将泛型类型作为函数参数,又将泛型类型作为函数的输出,那么既不用out也不用in。

父类泛型对象可以赋值给子类泛型对象,用in。
子类泛型对象可以赋值给父类泛型对象,用out。

在这里插入图片描述

//out
interface Production<out T>{
    fun product(): T
}

//in
interface Consume<in T>{
    fun consume(item: T)
}

interface ProductionConsumer<T>{
    fun product(): T
    fun consume(item: T)
}

open class Food
open class FastFood: Food()
class Burger: FastFood()

//生产者
//食品商店
class FoodStore: Production<Food>{
    override fun product(): Food {
        println("Produce food.")
        return Food()
    }
}

//快餐餐店
class FastFoodStore: Production<FastFood>{
    override fun product(): FastFood {
        println("Produce FastFood.")
        return FastFood()
    }
}

//汉堡商店
class BurgerFoodStore: Production<Burger>{
    override fun product(): Burger {
        println("Produce FastFood.")
        return Burger()
    }
}

//消费者
class Everybody: Consume<Food>{
    override fun consume(item: Food) {
        println("Eat food.")
    }
}

class ModernPeople: Consume<FastFood>{
    override fun consume(item: FastFood) {
        println("Eat fastFood.")
    }
}

class American: Consume<Burger>{
    override fun consume(item: Burger) {
        println("Eat burger")
    }
}

fun main() {
    //子类泛型对象可以赋值给父类泛型对象,用out。
    val foodStore:Production<Food> = FoodStore()
    val foodStore2:Production<Food> = FastFoodStore()
    val foodStore3:Production<Food> = BurgerFoodStore()
    //父类泛型对象可以赋值给子类泛型对象,用in
    val consume: Consume<Burger> = Everybody()
    val consume2: Consume<Burger> = ModernPeople()
    consume2.consume(Burger())  //"Eat fastFood."
    val consume3: Consume<Burger> = American()
}

十六、reified


有时候,你可能想知道某个泛型参数具体是什么类型,reified关键字能帮你检查泛型参数类型。Kotlin不允许对泛型参数T做类型检查,因为泛型参数会被类型擦除,也就是说,T的类型信息在运行是不可知的,Java也有这样的规则。

class MagicBox<T: Human>(){
    //产生一个指定类型的对象,就通过backup函数生成一个指定类型的对象
    inline fun <reified T> randomOrBackup(backup: ()->T):T{
        val items = listOf(
            Boy("Jack", 20),
            Man("John", 20)
        )
        val random = items.shuffled().first()
        return if(random is T){
            random
        }else{
            backup()
        }
    }
}

open class Human(val age: Int)
class Boy(val name:String, age:Int): Human(age)
class Man(val name:String, age:Int): Human(age)

fun main() {
    val box1: MagicBox<Man> = MagicBox()
    //由backup函数,推断出T的类型
    val subject: Man = box1.randomOrBackup {
        Man("Jimmy", 38)
    }
    println(subject)
}

十七、扩展函数


扩展可以在不直接修改类定义的情况下增加类功能,扩展可以用于自定义类,也可以用于自定义类,也可以用于比如List,String,以及Kotlin标准库里的其他类。和继承相似,扩展也能共享类行为,在你无法接触某个类定义,或者某个类没有使用open修饰符,导致你无法继承它时,扩展就是增加类功能的最好选择。

//全局模式下都有效果
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)

fun Any.easyPrint() = println(this)

fun main() {
    val addExt = "abc".addExt(2)
    15.easyPrint()
}

定义扩展函数和定义一般函数差不多,但有一点大不一样,除了函数定义,你还需要指定接受功能扩展的接收者类型。

在addExt扩展函数之前和之后分别打印字符串

fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)

fun String.easyPrint(): String{
    println(this)
    return this
}

fun main() {
    val addExt = "abc".easyPrint().addExt(2).easyPrint()
}

使用泛型来改造上面demo。

17.1、泛型扩展函数

新的泛型扩展函数不仅可以支持任何类型的接收者,还保留了接收者的类型信息,使用泛型类型后,扩展函数能够支持更多类型的接收者,适用范围更广了。

fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)

fun <T> T.easyPrint(): T{
    println(this)
    return this
}

fun main() {
    val addExt = "abc".easyPrint().addExt(2).easyPrint()
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值