学习Kotlin~类

类的field

  • 类定义的每一个属性,kotlin都会产生一个filed,一个setter(),一个getter()
  • field用来存储属性数据,不能直接定义,kotlin会封装,保护它里面数据,只暴露给getter和setter使用
  • 只有可变属性才有setter方法
  • 需要控制如何读取属性数据时,可以自定义它们
	class Player {
	    //针对每定义一个属性,都会有一个field,get(),set()
	    var name = "abc "
	        get() = field.capitalize()
	        set(v) {
	            field = v.trim()
	        }
	
	    //计算属性是通过一个覆盖的get()和set()来计算
	    var rolledValue = 0
	        get() = (1..6).shuffled().first()
	        set(v) {
	            field = v + 11
	        }
	}

类初始化

主构造函数

  • 主构造函数里,临时变量通常都会以下划线开头名字命名
	/**
	 * 主构造函数里,临时变量,通常都会以下划线开头的名字命名
	 */
	class Player(
	    _name: String,
	    _age: Int,
	    _isNormal: Boolean
	) {
	
	    var name = _name
	        get() = field.capitalize()
	        private set(value) {
	            field = value.trim();
	        }
	    var age = _age;
	    var isNormal = _isNormal;
	
	}
  • 主构造函数里定义属性,直接用一个变量和类型指定属性
	/**
	 * 在主构造函数里直接定义属性
	 */
	class Player1(
	    _name: String,
	    var age: Int,
	    val isNormal: Boolean
	) {
	
	    var name = _name
	        get() = field.capitalize();
	        private set(value) {
	            field = value.trim();
	        }
	
	}
  • 主构造函数里定义属性,可以给构造函数参数指定默认值
	/**
	 * 在主构造函数里直接定义属性
	 */
	class Player1(
	    _name: String,
	    var age: Int,
	    val isNormal: Boolean = true
	) {
	
	    var name = _name
	        get() = field.capitalize();
	        private set(value) {
	            field = value.trim();
	        }
	
	}

次构造函数

  • 可以定义多个次构造函数来配置不同的参数组合

  • 使用次构造函数,定义初始化代码逻辑

	/**
	 * 次构造函数
	 */
	class Player2(
	    _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, age = 100, isNormal = false) {
	        this.name = name.toUpperCase();
	    }
	}

初始化块

  • 初始化块可以设置变量或值,以及有效性检查

  • 初始化块代码会在构造类实例时执行

	/**
	 * 初始化块init,会在构造类实例时执行
	 */
	class Player3(
	    _name: String,
	    var age: Int = 20,
	    private val isNormal: Boolean
	) {
	    var name = _name
	        get() = field.capitalize();
	        private set(value) {
	            field = value.trim();
	        }
	
	    init {
	        require(age > 0) { "age must be positive" }
	        require(name.isNotBlank()) { "player must have a name" }//false会执行后面闭包
	    }
	}
初始化顺序

#mermaid-svg-LVJxVp9vnvbjq6cL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL .error-icon{fill:#552222;}#mermaid-svg-LVJxVp9vnvbjq6cL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LVJxVp9vnvbjq6cL .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-LVJxVp9vnvbjq6cL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LVJxVp9vnvbjq6cL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LVJxVp9vnvbjq6cL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LVJxVp9vnvbjq6cL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LVJxVp9vnvbjq6cL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LVJxVp9vnvbjq6cL .marker.cross{stroke:#333333;}#mermaid-svg-LVJxVp9vnvbjq6cL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LVJxVp9vnvbjq6cL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL .cluster-label text{fill:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL .cluster-label span{color:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL .label text,#mermaid-svg-LVJxVp9vnvbjq6cL span{fill:#333;color:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL .node rect,#mermaid-svg-LVJxVp9vnvbjq6cL .node circle,#mermaid-svg-LVJxVp9vnvbjq6cL .node ellipse,#mermaid-svg-LVJxVp9vnvbjq6cL .node polygon,#mermaid-svg-LVJxVp9vnvbjq6cL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LVJxVp9vnvbjq6cL .node .label{text-align:center;}#mermaid-svg-LVJxVp9vnvbjq6cL .node.clickable{cursor:pointer;}#mermaid-svg-LVJxVp9vnvbjq6cL .arrowheadPath{fill:#333333;}#mermaid-svg-LVJxVp9vnvbjq6cL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LVJxVp9vnvbjq6cL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LVJxVp9vnvbjq6cL .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-LVJxVp9vnvbjq6cL .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-LVJxVp9vnvbjq6cL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LVJxVp9vnvbjq6cL .cluster text{fill:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL .cluster span{color:#333;}#mermaid-svg-LVJxVp9vnvbjq6cL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LVJxVp9vnvbjq6cL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

主构造函数里声明的属性
类级别的属性赋值
init初始化块里的属性赋值和函数调用
次构造函数里属性赋值和函数调用

在这里插入图片描述

延迟初始化
  • 使用lateinit关键字相当于做了一个约定:再用它之前负责初始化
  • 只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查是否完成了初始化
  • 一般变量必须要初始化,但是使用lateinit以后可以先不用初始化,等到用的时候再去赋值
	/**
	 * 延迟初始化 lateinit关键字相当于做了一个约定:再用它之前负责初始化
	 * 只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查
	 */
	class Player5 {
	    lateinit var equipment: String
	    fun ready() {
	        equipment = "sharp knife"
	    }
	
	    //::操作符 使用变量的引用
	    fun battle() {
	        if (::equipment.isInitialized) println(equipment)
	    }
	}
惰性初始化
  • 暂时不初始化某个变量,直到首次使用它,这个叫惰性初始化
	/**
	 * 惰性初始化,可以暂时不初始化某个变量,直到首次使用它才初始化
	 */
	class Player6(_name: String) {
	    var name = _name
	
	    //val config =  loadConfig() ;//这种方式config变量直接初始化了
	    val config by lazy { loadConfig() }//这里使用by lazy就是惰性初始化
	
	    private fun loadConfig(): String {
	        println("loading...")
	        return "xxx"
	    }
	}
	
	fun main() {
	    //创建对象的时候,就会给所有的对象初始化,
	    //但是使用了by lazy以后的变量就可以不初始化,等到调用的时候自动初始化
	    val p = Player6("jack")
	    Thread.sleep(3000)
	    println(p.config)
	}

继承

  • 类默认都是封闭的,要想让某个类开放继承,必须使用open关键字修饰它
	/**
	 * 类默认是关闭的,要让某个类开放继承,必须使用open关键字修饰它
	 */
	open class Product(val name: String) {
	    fun description() = "Product $name"
	    open fun load() = "Nothing..."
	
	}

函数重载

  • 父类的函数也要以open关键字修饰,子类才能覆盖它
	//继承
	class LuxuryProduct(val _name: String) : Product(_name) {
	    /**
	     * 父类的函数也要以open关键字修饰,子类才能覆盖它
	     */
	    override fun load() = "LuxuryProduct loading ..."
	
	    fun sale(product: Product) {
	        println(product.description())
	    }
	}

类型检测

  • 每一个类都会继承一个共同的叫作Any的超类
is运算符
  • kotlin的is运算符是个不错的工具,可以用来检查某个对象的类型
	val p = LuxuryProduct("jack")
	/**
	 * is运算符可以用来检查某个元素类型
	 */
	println(p is LuxuryProduct)
	println(p is Product)
as运算符
  • as操作符声明,这是一个类型转换

  • 只要能确定any is父类条件检查属实,它就会将any当做子类类型对待,可以不经过as转换

	val p = LuxuryProduct("jack")
	/**
	 * as操作符,类型转换
	 */
	p.sale(p as LuxuryProduct)
	/**
	 * 智能类型转换
	 */
	p.sale(p)//这里不用转,默认是子类

单例对象

object关键字

  • 使用object关键字,可以定义一个只能产生一个实例的类-单例
  • 使用object关键字有三种方式
对象声明
  • 对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内某些一致性状态
	/**
	 * object关键字 对象声明 这是一个单例对象
	 */
	object ApplicationConfig {
	    //第一次创建时候执行
	    init {
	        println("loading config...")
	    }
	
	    fun setSomething() {
	        println("setSomething")
	    }
	}
	
	fun main() {
	    ApplicationConfig.setSomething()
	    println(ApplicationConfig)
	    println(ApplicationConfig)
	}
对象表达式
  • 可以使用object声明某个类的子类实例对象,不用在重新写一个新类;创建对象时候,对象的类名也省略了
	open class Player {
	    open fun load() = "loading nothing."
	}
	
	fun main() {
	    /**
	     * object关键字,声明一个匿名实例对象,也是单例
	     * 这个对象,是 Player的子类对象,子类无需定义一个名字
	     */
	    val p = object : Player() {
	        override fun load() = "anonymous class load..."
	    }
	    println(p.load())
	
	}
伴生对象
  • 一个类里只能有一个伴生对象
  • 想将某个对象的初始化和一个类实例捆绑在一起,可以考虑伴生对象,使用companion修饰符
	import java.io.File
	
	/**
	 * object 关键字 伴生对象
	 * 使用companion修饰,一个类只能有一个伴生对象
	 */
	open class ConfigMap {
	    companion object {
	        /**
	         * 只有初始化ConfigMap类或调用load函数时,伴生对象的内容才会载入。
	         * 而且无论实例化ConfigMap类多少次,这个伴生对象始终只有一个实例存在。
	         */
	        private const val PATH = "xxx"
	
	        fun load() = File(PATH).readBytes()
	    }
	}
	
	fun main() {
	    println(ConfigMap.load())
	}

运算符重载

  • 要将内置运算符应用在自定义类身上,必须重写运算符函数,告诉编译器如何操作自定义类
操作符 函数名 作用
+ plus 把一个对象添加到另一个对象里
+= plusAssign 把一个对象添加到另一个对象里,然后将结果赋值给第一个对象
== equals 两个对象相等则返回true,否则false
> compareTo 左边对象大于右边对象返回true,否则返回false
[] get 返回集合中指定位置的元素
rangeTo 创建一个range对象
in contains 如果对象包含在集合里,则返回true
	class Coordinate(var x: Int, var y: Int) {
	
	//    operator fun plus(c: Coordinate): Coordinate {
	//        return Coordinate(this.x + c.x, this.y + c.y);
	//    }
	
	    operator fun plusAssign(c: Coordinate) {
	        this.x = this.x + c.x;
	        this.y = this.y + c.y;
	    }
	    
	}
	
	fun main() {
	    var a = Coordinate(10, 20);
	    var c = Coordinate(1, 2);
	//    println(c + a)
	    a += c
	}

嵌套类

  • 如果一个类对另一个类有用,那么将其嵌入到该类中,并保持在一起是合乎逻辑的
	/**
	 * 嵌套类
	 */
	class Player3() {
	
	    class Equipment(val name: String) {
	        fun show() = println("equipment $name");
	    }
	
	    fun battle() {
	        Equipment("AK7").show();
	    }
	
	}
	
	fun main() {
	    Player3().battle();
	}

数据类

数据类的对象

  • 数据类是专门用来设计存储数据的类
  • 数据类提供了toString的个性化实现
  • ==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hashCode个性化实现
	/**
	 * 数据类, 专门用来存储数据的类
	 * 数据类提供了toString的个性化实现
	 * ==符号默认情况下,比较对象就是比较它们的引用,
	 * 数据类提供了equals和hashCode的个性化实现
	 */
	data class Coordinate(var x: Int, var y: Int) {
	    //坐标值是否是正值
	    val isInBounds = x >= 0 && y >= 0
	}
	
	fun main() {
	    //重写了toString方法
	    println(Coordinate(1, 5))//Coordinate(x=1, y=5)
	    //本身重写了equals和hashCode所以两个对象相等
	    println(Coordinate(1, 5) == Coordinate(1, 5))//true
	
	}

数据类的copy方法

  • 使用数据类的copy方法默认的是主构造函数,复制一个对象
	/**
	 * 数据类提供了一个copy函数可以用来方便的复制对象
	 */
	data class Student(var name: String, var age: Int) {
	    var score = 10
	    private val hobby = "music"
	    val subject: String
	
	    init {
	        println("initializing student")
	        subject = "math"
	    }
	
	    constructor(_name: String) : this(_name, 10) {
	        score = 20
	    }
	
	    override fun toString(): String {
	        return "Student(name='$name', age=$age, score=$score, hobby='$hobby', subject='$subject')"
	    }
	
	}
	
	fun main() {
	    val s = Student("Jack")
	    println(s)
	    val copy = s.copy("Rose")//这里复制对象默认使用的主构造函数
	    println(copy)
	}

数据类的解构声明

  • 结构声明的后台实现就是声明component1、component2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,类似这样
	public final int component1() {
	   return this.x;
	}
	
	public final int component2() {
	   return this.y;
	}
	/**
	 * 结构声明的后台实现就是声明component1,component2等若干个组件函数,让每个函数
	 * 负责管理你想返回的一个属性数据
	 */
	class PlayerScore(val experience: Int, val level: Int) {
	    operator fun component1() = experience
	    operator fun component2() = level;
	}
	
	fun main() {
	    val (x, y) = PlayerScore(10, 5)
	    println(x)
	    println(y)
	}
  • 如果定义一个数据类,它会自动为定义在主构造函数的属性添加对应的组件函数
	data class Coordinate(var x: Int, var y: Int) {
	    //坐标值是否是正值
	    val isInBounds = x >= 0 && y >= 0
	}
	
	fun main() {
	    /**
	     * 数据类天生支持解构语法,数据类默认会生成组件函数component1
	     */
	    val (x, y) = Coordinate(10, 5)
	    println(x)//10
	    println(y)//5
	}

数据类的使用条件

  • 经常需要比较、复制、打印自身内容的类,数据类适合它们;

  • 数据类使用有以下三个条件

    • 数据类必须有至少带一个参数的主构造函数
    • 数据类主构造函数的参数必须是val或var
    • 数据类不能使用abstract、open、sealed和inner修饰符

枚举类

  • 用来定义常量集合的一种特殊类
	enum class Direction {
	    EAST,
	    WEST,
	    SOUTH,
	    NORTH
	}
  • 枚举类也可以定义函数
	/**
	 * 枚举类也可以定义函数
	 */
	enum class Direction2(private val coordinate: Coordinate) {
	    //枚举类构造函数传入对象,那么每个枚举类的对象也要传入构造对象
	    EAST(Coordinate(5, -1)),
	    WEST(Coordinate(1, 0)),
	    SOUTH(Coordinate(0, 1)),
	    NORTH(Coordinate(-1, 0));
	
	    fun updateCoordinate(p: Coordinate) = Coordinate(p.x + coordinate.x, p.y + coordinate.y)
	
	}
	
	class Coordinate(val x: Int, val y: Int) {
	    override fun toString(): String {
	        return "Coordinate(x=$x, y=$y)"
	    }
	}
	
	fun main() {
	    println(Direction.EAST)
	    //调用函数时,使用的是枚举常量,所以这样调用
	    println(Direction2.EAST.updateCoordinate(Coordinate(1, 2)))
	}

下面使用枚举类实现一个驾照类和司机类;

	/**
	 * 代数数据类型
	 */
	enum class LicenseStatus {
	    UNQUALIFIED,//没资格
	    LEARNING,//正在学
	    QUALIFIED;//有驾照
	
	    //驾驶证的id,这里的话只有有驾照才有其他的都不可能有
	   // var licenseId: String? = null;
	
	}
	
	class Driver(var status: LicenseStatus) {
	    fun checkLicense(): String {
	        return when (status) {
	            LicenseStatus.UNQUALIFIED -> "没资格"
	            LicenseStatus.LEARNING -> "在学"
	            LicenseStatus.QUALIFIED -> "有资格"
	        }
	    }
	}

密封类

  • 枚举类和密封类都是代数数据类型(ADT);

在上面的枚举类中不可能正常带有驾照的id,为了实现这种需求,我们使用了密封类

  • 密封类可以有若干个子类,若要继承密封类,这些子类必须和它定义在同一个文件里;
	/**
	 * 密封类
	 * 可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里
	 */
	sealed class LicenseStatus {
	    //这种情况使用object单例,因为没有属性状态
	    object UnQualified : LicenseStatus2()
	    object Learning : LicenseStatus2()
	
	    //有属性状态,所以使用类
	    class Qualified(val licenseId: String) : LicenseStatus2()
	}
	
	class Driver(var status: LicenseStatus2) {
	    fun checkLicense(): String {
	        //编译器会自动检测是否有遗漏
	        return when (status) {
	            LicenseStatus2.UnQualified -> "没资格"
	            LicenseStatus2.Learning -> "在学"
	            is LicenseStatus2.Qualified ->
	                "有资格,驾驶证编号:" + "${(this.status as LicenseStatus2.Qualified).licenseId}"
	        }
	    }
	}

使用了密封类,就可以正常展示驾照的Id了;

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>