一、主构造函数
我们在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]
}
十五、out
,in
,invariant 不变
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()
}