类
类的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会执行后面闭包
}
}
初始化顺序
延迟初始化
- 使用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了;