文章目录
集合
集合可以方便处理一组数据,也可以作为值参传给函数,和我们学过的其他变量类型一样,List,Set和Map类型的变量也分为两类,只读和可变
List集合
List的创建与元素的获取
list通过listOf来创建
getOrElse是一个**安全索引取值函数,**它需要两个参数,第一个是索引值,第二个是能提供默认值的lambda表达式,如果索引值不存在的话,可用来代替异常。
getOrNull是Kotlin提供的另一个安全索引取值函数,它返回null结果,而不是抛出异常
//LIST集合
fun main() {
val list:List<String> = listOf("Jason", "Jack", "Jacky")
println(list[2]) //普通的按索引取元素 越界会报错
println(list.getOrElse(3){"Unknown"}) //找不到元素会执行后面的lambda式
println(list.getOrNull(3)?:"Unknown") //找不到元素返回null 可以和安全操作符一起使用
}
可变列表
在Kotlin中,支持内容修改的列表叫可变列表,要创建可变列表,可以使用mutableListOf函数。List还支持使用toList和toMutableList函数动态实现只读列表和可变列表的相互转换。
//可变集合
fun main() {
val mutableList = mutableListOf("Jason", "Jack", "Jacky")
mutableList.add("Jimmy")
mutableList.remove("Jack")
println(mutableList)
//不可变转可变
listOf("Jason", "Jack", "Jacky").toMutableList()
//可变转不可变
mutableListOf("Jason", "Jack", "Jacky").toList()
}
mutator函数
能修改可变列表的函数有个统一的名字:mutator函数
添加元素运算符与删除元素运算符
基于lambda表达式指定的条件删除元素
//可变集合
fun main() {
val mutableList = mutableListOf("Jason", "Jack", "Jacky")
//不可变转可变
listOf("Jason", "Jack", "Jacky").toMutableList()
//可变转不可变
mutableListOf("Jason", "Jack", "Jacky").toList()
//跟add是一样的效果
mutableList += "Jimmy"
mutableList -= "Jason"
//如果字符串中包含jack 就移除这个字符串
mutableList.removeIf{ it.contains("Jack")}
println(mutableList)
}
集合遍历
for in 遍历
forEach 遍历
forEachIndexed 遍历时要获取索引
//函数遍历
fun main() {
val list = listOf("Jason", "Jack", "Jacky")
//for循环
for(s in list) {
println(s)
}
//forEach
list.forEach{
print(it)
}
//遍历下标
list.forEachIndexed{index, item ->
println("$index, $item")
}
}
解构
通过_符号过滤不想要的元素
一次性通过集合给多个元素赋值
//_代表跳过当前元素 当前元素不使用 直接给下一个字符串赋值即可
//将第一个元素赋值给origin 第二个元素不使用 第三个元素赋值给proxy
//将集合中的元素赋值 可以比集合短
val(origin:String,_:String) = list
println("$origin")
Set集合
不允许有重复的元素
set创建与元素获取
通过setOf创建set集合,使用elementAt函数读取集合中的元素
//set集合
fun main() {
val set = setOf("Jason", "Jack", "Jacky", "Jack")
//取第二号元素
println(set.elementAt(2))
}
可变集合
通过mutableSetOf创建可变的set集合
//创建可变集合
val mutableSet = mutableSetOf("Jason", "Jack", "Jacky", "Jack")
mutableSet += "Jam"
集合转换
把List换成Set,去掉重复元素
快捷函数
//将list转为set 去重
val list = listOf("Jason", "Jack", "Jacky", "Jack")
.toSet()
.toList()
println(list)
//list转set快捷函数
println(listOf("Jason", "Jack", "Jacky", "Jack").distinct())
数组类型
Kotlin提供了各种Array,虽然是引用类型,但可以编译成Java基本数据类型
import java.io.File
//数组类型
fun main() {
//int数组
val intArrayOf = intArrayOf(10, 20, 121, 1)
//list转int型数组
listOf(10, 20, 121, 1).toIntArray()
//File数组
val arrayOf = arrayOf(File("xxx"), File("yyy"))
}
Map集合
to看上去像关键字,但事实上,它是个省略了点号和参数的特殊函数,to函数将它左边和右边的值转化为一对Pair
//map集合
fun main() {
//根据to创建map
//to是一个函数 将它左边和右边的值转化为一对Pair
val mapOf = mapOf("Jack" to 20, "Jason" to 18, "Jack" to 30)
//可以直接根据Pair创建map
mapOf(Pair("Jimmy", 10))
}
读取Map的值
[] 取值运算符,读取键对应的值,如果键不存在就返回null
getValue 读取键对应的值,如果键不存在就抛出异常
getOrElse 读取键对应的值 或者使用匿名函数返回默认值
getOrDefault 读取键对应的值 或者返回默认值
//根据to创建map
//to是一个函数 将它左边和右边的值转化为一对Pair
val map = mapOf("Jack" to 20, "Jason" to 18, "Jack" to 30)
println(map["Jack"])
println(map.getValue("Jack"))
println(map.getOrElse("Rose"){"UnKnown"}) //如果没有 返回值就用lambda表达式的值 也就是Unknown
println(map.getOrDefault("Rose", 0)) //如果没有元素 返回值为第二个参数(默认值) 0
遍历
两种方式 forEach遍历Map
//forEach遍历
map.forEach{
print("${it.key}, ${it.value}")
}
map.forEach{ (key: String, value: Int) ->
println("$key, $value" )
}
可变集合
通过mutableMapOf创建可变的Map
getOrPut键值不存在,就添加并返回结果,否则就返回已有键对应的值
//可变的map集合
fun main() {
val mutableMap = mutableMapOf("Jack" to 20, "Jason" to 18, "Jack" to 30)
//向map中添加(Jimmy 20)
mutableMap += "Jimmy" to 20
//将前面的覆盖
mutableMap.put("Jimmy", 30)
//如果没有Rose 就添加一个 如果有就获取value
mutableMap.getOrPut("Jimmy"){18}
println(mutableMap)
}
类
定义类
field
针对你定义的每一个属性,Kotlin都会产生一个field,一个getter,以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用,属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们。
field取得的是属性的数据
//定义类
class Player {
//自动生成get和set方法 如果是非可空类型 NotNull 如果是可空类型 Nullable
//如果有多个属性 使用field就在属性后添加即可
var name:String = "Jack"
get() = field.capitalize()
set(value) {
field = value.trim()
}
var age = "Capper"
get() = field.capitalize()
set(value) {
field = value.trim()
}
}
fun main() {
var p = Player()
p.name = " rose "
println(p.name)
}
计算属性
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要啦
//定义类
class Player {
//计算属性 不需要取出属性的值
var age = 12
get() = (1..6).shuffled().first()
}
防范竞态条件
如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数。
//定义类
class Player {
var name:String = "Jack"
fun saySomething() {
name?.also {
println("Hello ${it.toUpperCase()}")
}
}
}
初始化
主构造函数
我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。
class Player (
//在类后面的括号内写主构造函数的参数
//以下划线开头的代表只用一次 临时变量
_name: String,
_age: Int,
_isNormal: Boolean
){
//将一个属性或者方法private后 后面就不可调用了
var name = _name
get() = field.capitalize()
set(value) {
field = value.trim()
}
var age = _age
var isNormal = _isNormal
}
fun main() {
val p = Player("Jack", 20, true)
p.name = "rose"
}
在主构造函数里定义属性
Kotlin允许你不使用临时变量赋值,而是直接用一个定义同时指定参数和类属性,通常,我们更喜欢用这种方式定义类属性,因为他会减少重复代码。
//主构造函数
class Player2 (
//在类后面的括号内写主构造函数的参数
//以下划线开头的代表只用一次 临时变量
_name: String,
//在主构造函数里定义属性 直接用一个定义同时指定参数和类属性
var age: Int,
var isNormal: Boolean
){
//将一个属性或者方法private后 后面就不可调用了
var name = _name
get() = field.capitalize()
set(value) {
field = value.trim()
}
}
fun main() {
val p = Player2("Jack", 20, true)
p.name = "rose"
}
反编译为Java
public final class Player2 {
@NotNull
private String name;
private int age;
private boolean isNormal;
@NotNull
public final String getName() {
return StringsKt.capitalize(this.name);
}
public final void setName(@NotNull String value) {
Intrinsics.checkNotNullParameter(value, "value");
boolean var3 = false;
this.name = StringsKt.trim((CharSequence)value).toString();
}
public final int getAge() {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
public final boolean isNormal() {
return this.isNormal;
}
public final void setNormal(boolean var1) {
this.isNormal = var1;
}
public Player2(@NotNull String _name, int age, boolean isNormal) {
Intrinsics.checkNotNullParameter(_name, "_name");
super();
this.age = age;
this.isNormal = isNormal;
this.name = _name;
}
}
次构造函数
可以定义多个次构造函数来配置不同的参数组合
//次构造函数 内部会调用主构造函数
constructor(name: String) : this(name, age = 10, isNormal = false)
使用次构造函数,定义初始化代码逻辑
//在次构造函数内定义初始化代码逻辑
constructor(name: String, age: Int) : this(name, age, isNormal = false) {
this.name = name.toUpperCase()
}
默认参数
定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值
//主构造函数
class Player3 (
_name: String,
//在构造函数中给默认值
var age: Int = 20,
var isNormal: Boolean
){
//将一个属性或者方法private后 后面就不可调用了
var name = _name
}
fun main() {
//如果要跳过中间的参数 使用具名法
val p = Player3("Jack", isNormal = false)
println(p.age)
}
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行
和Java中的静态代码块不同
Java中的静态代码块是在类加载时运行的 初始化块代码会在构造类实例时执行
类加载机制
一个.java文件在编译后会形成相应的一个或多个Class文件(若一个类中含有内部类,则编译后会产生多个Class文件),但这些Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能被运行和使用。事实上,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程就是虚拟机的 类加载机制 。
类实例化
-
前提: .class文件已经被加载完毕(经过加载、解析和初始化)
-
系统赋初值:在堆内存中为Xxx类以及其父类(包括间接父类)中的
非静态成员变量
开辟空间,并赋系统默认初值
(0,null,false),因为成员变量有初值,函数才能使用。即使子类覆盖了父类成员变量,仍会给父类所有成员变量分配空间,此时子类中多了一个属性,分态性,分空间存储,分别存储父类和子类的成员变量
-
父类成员变量的显示初始化和构造代码块的初始化
两者的执行顺序就是代码排列顺序
(和静态变量显示初始化和静态构造代码块初始化一样)。但须注意,和前面静态一样,当构造代码块在前而成员变量声明在后时,构造代码块中只能赋值,不能访问
。另外,实际上两者会被自动装入父类构造函数的最前面
。 -
父类构造函数执行
Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有类的构造函数的第一条语句必须是超类构造函数
的调用语句,以保证所创建实例的完整性。 -
成员变量的显示初始化和子类构造代码块初始化
注意事项和父类一样。 -
构造函数初始化
每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义
构造函数,那么JVM会自动生成一个默认无参的
构造函数。如果自己定义了
,则不会再自动生成任何构造函数
。在编译生成的字节码中,这些构造函数会被命名成()方法,参数列表与Java语言书写的构造函数的参数列表相同。
类的加载机制和实例化的区别
//主构造函数
class Player3 (
_name: String,
//在构造函数中给默认值
var age: Int = -1,
var isNormal: Boolean
){
//将一个属性或者方法private后 后面就不可调用了
var name = _name
//初始化块
init {
//如果条件没有被满足 就会抛出后面的异常信息
require(age > 0) {"age must be positive"}
require(name.isNotBlank()) {"player must have a name."}
}
}
初始化顺序
主构造函数里声明的属性
类级别的属性赋值 + init初始化块里的属性赋值和函数调用 (按照顺序执行)
次构造函数里的属性赋值和函数调用
延迟初始化
告诉编译器该变量可以不用提前初始化
使用lateinit关键字相当于做了一个约定,在用它之前负责初始化
只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查
//延迟初始化
class Player4 {
//延迟初始化
lateinit var equipment: String
fun ready() {
equipment = "share knife"
}
fun battle() {
//进行判断 如果没有被初始化就不执行后面的
if(::equipment.isInitialized) println(equipment)
}
}
fun main() {
var p = Player4()
p.ready()
p.battle()
}
惰性初始化
可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化
与延迟初始化的区别
延迟初始化之前要先执行初始化操作 需要自己调用初始化方法
惰性初始化可以事先写好初始化函数 当需要调用该变量时会调用该初始化代码进行初始化
和单例模式中的懒汉式和饿汉式相同
懒汉式 是什么都不干 等到需要的时候在加载 lazy
饿汉式 是提前加载好需要的东西 等到用的时候直接拿
//惰性初始化
class Player5 (_name:String) {
var name = _name
//惰性初始化 懒汉式 等到三秒后在执行loadConfig()
//val config by lazy { loadConfig() }
//饿汉式 先执行loadConfig() 3秒后执行println(p.config)
val config = loadConfig()
private fun loadConfig() : String{
println("loading...")
return "xxx"
}
}
fun main() {
var p = Player5("Jack")
//线程睡3秒
Thread.sleep(3000)
println(p.config)
}
初始化陷阱一
类级别的代码块和init代码块按顺序执行 所以在init中初始化某些变量的时候需要注意有没有定义
在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化
初始化陷阱二
因为在使用变量前没有赋值 所以会报空指针异常
class Player6() {
val name:String
private fun firstLetter() = name[0]
init {
//name为空 需要先赋值
println(firstLetter())
name = "Jack"
}
}
fun main() {
Player6()
}
初始化陷阱三
//初始化陷进 赋值为空
class Player7(_name : String) {
val playerName:String = initPlayerName()
val name:String = _name
private fun initPlayerName() = name
}
fun main() {
//此时name为空
println(Player7("Jack").playerName)
}
继承
类默认都是封闭的,要让某个类开放继承,必须使用open关键字来修饰它
函数重写
父类函数也要以open关键字修饰,子类才能覆盖它
//类的继承
open class Product (val name:String) {
fun description() = "Product: $name"
//如果需要重写方法 也需要使用open修饰要重写的方法
open fun load() = "Nothing..."
}
class LuxuryProduct : Product("Lucury") {
//重写方法前需要添加override关键字
override fun load() = "LuxuryProduct loading..."
}
fun main() {
//类似于java中多态
val p:Product = LuxuryProduct()
println(p.load())
}
类型检测
使用is关键字来检查某个对象的类型
as修饰符来实现类型转化
//类的继承
open class Product (val name:String) {
fun description() = "Product: $name"
//如果需要重写方法 也需要使用open修饰要重写的方法
open fun load() = "Nothing..."
}
class LuxuryProduct : Product("Lucury") {
//重写方法前需要添加override关键字
override fun load() = "LuxuryProduct loading..."
fun special() = "LuxuryProduct special function"
}
fun main() {
//类似于java中多态
val p:Product = LuxuryProduct()
println(p.load())
println(p is Product)
println(p is LuxuryProduct)
println(p is File)
//向下转型 使用as操作符
if (p is LuxuryProduct) {
println((p as LuxuryProduct).special())
}
}
智能类型转换
Kotlin编译器只要能确定any is父类条件检查属实,他就会将any当做子类类型对待,因此,编译器允许你不经类型转换直接使用
//类的继承
open class Product (val name:String) {
fun description() = "Product: $name"
//如果需要重写方法 也需要使用open修饰要重写的方法
open fun load() = "Nothing..."
}
class LuxuryProduct : Product("Lucury") {
//重写方法前需要添加override关键字
override fun load() = "LuxuryProduct loading..."
fun special() = "LuxuryProduct special function"
}
fun main() {
//类似于java中多态
val p:Product = LuxuryProduct()
//转型转一次后 p就指向这个转化后的对象了
println((p as LuxuryProduct).special())
p.special()
}
Kotlin层次
每一个类都会继承一个共同的叫作Any的超类 基类:Any