类
类的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 = 0get() = (1..6).shuffled().first()set(v) {field = v + 11}}
类初始化
主构造函数
- 主构造函数里,临时变量通常都会以下划线开头名字命名
/*** 主构造函数里,临时变量,通常都会以下划线开头的名字命名*/class Player(_name: String,_age: Int,_isNormal: Boolean) {var name = _nameget() = 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 = _nameget() = field.capitalize();private set(value) {field = value.trim();}}
- 主构造函数里定义属性,可以给构造函数参数指定默认值
/*** 在主构造函数里直接定义属性*/class Player1(_name: String,var age: Int,val isNormal: Boolean = true) {var name = _nameget() = field.capitalize();private set(value) {field = value.trim();}}
次构造函数
-
可以定义多个次构造函数来配置不同的参数组合
-
使用次构造函数,定义初始化代码逻辑
/*** 次构造函数*/class Player2(_name: String,var age: Int,val isNormal: Boolean) {var name = _nameget() = 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 = _nameget() = 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会执行后面闭包}}
初始化顺序
延迟初始化
- 使用lateinit关键字相当于做了一个约定:再用它之前负责初始化
- 只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查是否完成了初始化
- 一般变量必须要初始化,但是使用lateinit以后可以先不用初始化,等到用的时候再去赋值
/*** 延迟初始化 lateinit关键字相当于做了一个约定:再用它之前负责初始化* 只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查*/class Player5 {lateinit var equipment: Stringfun 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 = 10private val hobby = "music"val subject: Stringinit {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() = experienceoperator 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)//10println(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了;