-
类的构造器
1.主构造器
①init块
init块会随着主构造器一起运行:
class LoadT(var inf:Any,image:Int){
init {
inf= ""
}
init {
inf = image
}
}
相当于:
class LoadT(var inf:Any,image:Int){
init {
inf= ""
inf = image
}
}
②属性初始化:
在类里面定义属性必须初始化
class LoadT(var inf:Any,image:Int){
val tt // 这里的tt必须要初始化,不然会报错。
}
2.副构造器
①如果一个类有主构造器,那么所有的副构造器都必须通过this调用主构造器
:
class LoadT(var inf:Any,image:Int){
constructor(gender:String) : this("", image = 1){
}
}
如果有多个构造器,副构造器可以调用主构造器,或者调用已经调用了主构造器的副构造器
:
class LoadT(var inf:Any,image:Int){
constructor(gender:String) : this("", image = 1){
}
constructor():this(""){
}
}
②也可以不定义主构造器,只定义副构造器。
但是不推荐,会有多条构造路径,增加复杂度。
class LoadT1{
var inf:Int
var image:String
constructor():super(){ //如果父类的主构造器没有,可以省略super()
this.inf = 5
image = "" //这里省略了this,但是效果是一样的。
}
}
③当你没有主构造器的时候,有多个副构造器。
副构造器,可以调用已经调用的主构造器的副构造器。
:
class LoadT1{
var inf:Int
var image:String
constructor(gender:String):super(){ //如果父类的主构造器没有,可以省略super()
this.inf = 5
image = "" //这里省略了this,但是效果是一样的。
}
constructor():this(""){
}
}
④ 主构造器默认参数
如果希望主构造器参数在java代码中用重载的方式调用:
在constructor的前面添加关键字@JvmOverloads
class LoadT1{
var inf:Int = 5
var image:String
@JvmOverloads //实际上是定义在constructor的前面。
constructor(gender:String):super(){
image = ""
}
constructor():this(""){
}
}
3.构造工厂函数。
工厂函数可以加载缓存或者在构建类的实例的时候,类的实例是私有的,需要借助工厂函数创建。
:
val loadT = HashMap<String,LoadT>()
fun LoadTCatch(image: String):LoadT{ //通过LoadTCatch缓存对象
return loadT[image]?: LoadT(1,image).also { loadT[image] = it }
}
return 如果不为空,返回前面的loadT[image],如果为空返回后面的内容。
类与成员可见性
1.与java对比的可见性
Public很好理解,就是所有的都能访问到。
internal 修饰的仅模块内可见,模块内指的是同一个gradle或者maven项目.
Protected 修饰的只能在类或者子类中可见,通过包下面的类看不见:
class Os(){
internal val i = 5
protected val c= ""
}
fun oo(){
val os = Os()
os.i
os.c //这里报错看不见
}
private 如果修饰的是顶级声明或者类,可见范围是文件可见。如果修饰的是成员,则是类中可见,即使在同一个包下面,也是不可见的。
下面的截图就是两个不同的文件。
2.虽然声明了internal关键字,但是用java还是能访问到。除非加上@JvmName(“”):
class Osajdoi(){
internal val i = 5
private val c= ""
@JvmName("!daojcs") internal fun ia(){ //@JvmName标识只能放在方法中,属性不行。
}
}
3.在属性中的get可见性必须和属性保持一致:
class Osajdoi(){
var i :Int= 5
get() {
return field //get的可见性必须和属性保持一致
}
set(value) {
field=value //set的可见性必须小于属性的可见性
}
}
类属性的延迟初始化
在kotlin中,属性必须在构造的时候必须初始化,但是像Android的UI绑定,需要在使用的时候再绑定。
所以就需要延迟初始化。
延迟初始化有三种方式:
1.跟java类似,初始化为null:
class MainActivity : ComponentActivity() {
var nameText: TextView? = null //通过?的方式表示有可能为空
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nameText = findViewById(androidx.core.R.id.text)
}
}
这样做不好的地方在于,每次调用nameText的时候,都需要加上?
像这样 nameText?
2.使用lateinit 关键字,这个关键字加在权限的后面:
class MainActivity : ComponentActivity() {
lateinit var nameText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
If(::nameText.isInitialized){
nameText = findViewById(androidx.core.R.id.text)
}
}
}
使用这个关键字用来延迟初始化,注意没初始化之前使用会报错。
在kotlin1.2之后,使用引用判断是否为空 ::property.isInitialized
3.使用lazy进行延迟初始化
by lazy是放在property的最后面,并且把使用跟声明放在一起。
只有使用这个property才会调用里面的代码。:
class MainActivity : ComponentActivity() {
private val nameText: TextView by lazy {
findViewById(androidx.core.R.id.text) //只有使用nameText才会调用方法体里面。
}
}
代理Delegate
代理分为两种,接口代理和属性代理。
1.接口代理:在实现的后面加上by 和 实现的接口(跟在类的参数有关)。
可能有点抽象,看个例子吧。
这个例子是代理数组,实现一个超级数组,可以接受list,也可以接受map(map就是key-value)
:
class SuperArray<E>(
private val list:MutableList<E?> = ArrayList(), //创建可变的List,并且给初始化
private val map:MutableMap<Any,E> = HashMap() //创建可变的map,并且给初始化
):MutableList<E?> by list, MutableMap<Any,E> by map { //实现接口MutableList和MutableMap
override fun isEmpty(): Boolean =
list.isEmpty()&& map.isEmpty()
override val size: Int
get() = list.size+map.size
override fun clear() {
list.clear()
map.clear()
}
override fun toString(): String {
return "List{$list};Map:{$map}"
}
override operator fun set(index: Int, element: E?): E? {
if (list.size <= index){ //复写List中的Set逻辑,确保每个元素都能添进
repeat(index-list.size + 1){
list.add(null)
}
}
return list.set(index,element) //这里是调用List底层的set,不会形成无限循环
}
}
实际开发的过程中,代理接口的需求没有那么多,代理属性的需求很多。
2.属性代理
①lazy,lazy属性代理的实质是接口lazy的实例代理了对象属性的getter。
看着有点绕,看源码:
②属性代理中的observable
本质上是observable对象的实例代理了getter和setter
:
class User {
var age: Int by Delegates.observable(0) { property, oldValue, newValue ->
println("Property ${property.name} changed from $oldValue to $newValue")
}
}
0是初始化值,property是指这个属性,oldValue是之前的值,newValue是新的值。
用observable可以侦测值的变化。
③可以自定义属性代理,负责代理属性的getter和setter
如果属性是var,可以代理getter和setter
如果属性是val,只能代理getter
下面通过一个例子来写出怎么实现代理:
class Xi{
val x:Int by Agent()
var y:Int by Agent()
}
class Agent{
operator fun getValue(thisRef: Any?,property:KProperty <*>):Int{
return 3
}
operator fun setValue(xi: Xi,property: KProperty<*>,i:Int){
//如果确定要代理的类,可以把thisRef:Any?换成代理的类
}
}
vetoable也是在Delegates类中,不过是检查属性变化之前的。可以通过自己抒写逻辑,决定是否让属性的值变化:
import kotlin.properties.Delegates
class TemperatureSensor {
var temperature: Int by Delegates.vetoable(20) { _, oldValue, newValue ->
// 仅当新值在 -30 到 50 度之间时才允许更改
if (newValue in -30..50) {
println("Temperature changed from $oldValue to $newValue") true
} else {
println("Attempted to set invalid temperature: $newValue") false }
}
}
fun main() {
val sensor = TemperatureSensor() println(sensor.temperature) // 输出:20
sensor.temperature = 25 // 有效 println(sensor.temperature) // 输出:25
sensor.temperature = 60 // 无效 println(sensor.temperature) // 输出:25
sensor.temperature = -10 // 有效 println(sensor.temperature) // 输出:-10
}
使用属性代理读写Properties
在使用代理之前,需要了解Properties类,继承自Hashtable<Object,Object>,用于维护键值对,这些键值对通常是以字符串形式表示。
Properties类通常用来储存配置数据。
常用的方法有:
load(InputStream inStream)//从输入流中读取键值对
load(Read reader) //从字符输入流中读取属性列表
store(OutputStream out,String comments) // 将属性以输出流写出,comments是用来提示的。
store(Writer writer,String comments) //以字符流的方式输出,comments是用来提醒的。
常用的访问和修改属性的方法:
getProperty(String key) //根据键获得相应的值
getProperty(String key,String defaultValue) //根据键获得相应的值,如果键不存在,就返回默认的值。
SetProperty(String key,String value) //设置键值对
propertyName() //通过这个方法可以获取所有的键值对
实现效果:用属性代理的方式,获取或者改写配置文件。
class PropertiesDelegate(private val path: String, private val defaultValue: String = ""){
private lateinit var url: URL
·private val properties: Properties by lazy {
val prop = Properties()
url = try {
javaClass.getResourceAsStream(path).use {
prop.load(it)
}
javaClass.getResource(path)
} catch (e: Exception) {
try {
ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
prop.load(it)
}
ClassLoader.getSystemClassLoader().getResource(path)!!
} catch (e: Exception) {
FileInputStream(path).use {
prop.load(it)
}
URL("file://${File(path).canonicalPath}")
}
}
prop
}
//黄色标注部分是用来加载属性文件:
1.先尝试用当前类的类加载器从类路径中加载
2.如果加载失败,尝试使用系统类加载器从类路径中加载
3.如果仍失败,尝试从文件系统加载
operator fun getValue(thisRef: Any?,property: KProperty<*>):String{
return properties.getProperty(property.name,defaultValue)
}
operator fun setValue(thisRef: Any?,property: KProperty<*>,value:String){
properties.setProperty()
}
}
abstract class AbsProperties(path: String){
protected val absProperties = PropertiesDelegate(path) //生成PropertiesDelegate的实例
}
class Config :AbsProperties("Config.properties"){
var author by absProperties
var version by absProperties
var desc by absProperties //这里的属性是配置文件中存在的属性名
}
fun main(){
val config =Config()
println(config)
config.author = "me"
println(config.author)
}
用属性代理,实现获取和改写配置文件。
Android开发的过程中还经常会用到sharedPreferences。
写出sharedPreferences属性代理的方式。
一定要先去了解sharedPreferences的用法,不然会看不懂的。
这里篇幅的原因,就不赘述
:
class SharedPreDelegates<T>(private val name: String,
private val defaultValue: T,
private val sharedPreferences: SharedPreferences){
operator fun getValue(thisRef: Any?,property: KProperty<*>):T{
return when (defaultValue){
is Boolean -> sharedPreferences.getBoolean(name,defaultValue) as T
is Float -> sharedPreferences.getFloat(name,defaultValue) as T
is Int -> sharedPreferences.getInt(name,defaultValue) as T
is Long -> sharedPreferences.getLong(name,defaultValue) as T
is String -> sharedPreferences.getString(name,defaultValue) as T
else -> throw IllegalArgumentException("This type cannot be save")
}
}
operator fun setValue(thisRef: Any?,property: KProperty<*>,i: T){
with(sharedPreferences.edit()){
when(i){
is Boolean -> putBoolean(name,i)
is Float -> putFloat(name,i)
is Int -> putInt(name,i)
is Long -> putLong(name,i)
is String -> putString(name,i)
else -> throw IllegalArgumentException("")
}.apply()
}
}
}
abstract class AbsSharePreferences(context: Context){
private val shred: SharedPreferences =context.getSharedPreferences("MyPreferences",Context.MODE_PRIVATE)
protected fun <T> delegate(name: String,defaultValue: T):SharedPreDelegates<T>{
return SharedPreDelegates<T> (name,defaultValue,shred)
}
}
class SetShareP(context: Context):AbsSharePreferences(context){
var userName by delegate("userName","")
var isLoggedIn by delegate("isLoggenIn",false)
var userAge by delegate("passWord",0)
}
单例Object
- object的定义
object 就相当于是一个饿汉式的单例,Singleton既是类名也是对象名
饿汉式就是访问的时候会立即执行。
object Singleton{
}
- 成员访问
直接使用类名加.的方式访问,例如:
object Singleton{
var i :Int = 3
fun ig(){
}
}
fun oyy(){
Singleton.i
Singleton.ig()
}
- 伴生对象
在定义一个类的时候,还可以给它定义一个伴生对象。
伴生对象是与普通类同名的object,例如:
fun oyy(){
Room.o() //访问伴生对象
}
class Room{
companion object{
@JvmStatic fun o(){
}
}
}
4.两个注解,@JvmField和@JvmStatic
①对属性添加@JvmField后,对kotlin没有影响,可以正常访问。
但是对于java的影响只能访问Field,没有了getter和setter。
如果是在object或者是companion object里面,
设置带@JvmField的属性,转换成java代码都是带static。;
但如果是直接在类里面对属性设置@JvmField,转换成java代码没有static
注意,不换成java代码的话,对kotlin没有任何影响,因为kotlin中不存在static类。
例如:
class Room{ //无论是age还是male,转java后都只能访问其Filed
@JvmField var male = "" //转java后不会有static
companion object{
@JvmField var age= 5 //转java后会有static
}
}
②@JvmStatic
只能在object或者是companion object的使用这个注解。
可以对属性或者是函数使用,转换成java后,相当于加上了static
class Room{ //无论是age还是male,转java后都只能访问其Filed
companion object{
@JvmStatic var grade = "" // 转换成java后,属性加上static
@JvmStatic fun o(){ // 转换成java后,函数加上static
}
}
}
内部类
- 内部类的定义
默认在类中加类是静态内部类。
加上Inner关键字后,是非静态内部类。
class Outer{
inner class Inner{
//这里是非静态内部类,,访问需要持有外部实例的引用
}
class StaticInner {
//这里相当于是静态类内部类,在访问的时候可以直接通过类目加点
}
}
fun visit(){
var inner = Outer().Inner() //持有外部类的实例访问
var staticInner = Outer.StaticInner() //直接通过类名加.进行访问。
}
- 内部object
内部object是饿汉式,是立即执行,不存在非静态的情况,所以不能用inner修饰
访问可以通过类名加点的形式:
object Singleton{
object StaticInnerObject {
}
}
fun oyy(){
var visit = Singleton.StaticInnerObject
}
- 匿名内部类
object省略名字,在‘:’后面跟实现的接口类。
并且可以继承父类或实现多个接口
例如:
fun implements(){
object:Runnable,Cloneable {
override fun run() {
TODO("Not yet implemented") //继承Runnable方法
}
override fun clone(): Any {
return super.clone() //继承Cloneable方法
}
}
}
- 本地类(LocalClass)
可以在一个函数中,定义一个类实现多个接口。
例如:
fun implements(){
class LocalClass:Cloneable,Runnable{
override fun run() {
TODO("Not yet implemented")
}
}
}
- 本地函数
就是在函数里面套函数,很简单,就不写代码了。
- 交叉类型和联合类型。
对于本地类的访问类型,kotlin是不确定的。
实际上不是不确定,而是kotlin中没有,在TypeScript语言中,这是属于一个交叉类型
即Cloneable&Runnable 类型。
除此之外,还有Union类型,就是联合类型。
在表示一个类型可能是Cloneable或者是Runnable的时候,可以用下面的例子表示:
Cloneable| Runnable
这些交叉类型和联合类型虽然在kotlin中不支持,但是在TypeScript中支持,还有在现在如日中天的鸿蒙系统开发语言ArkTs语言中,也是支持的,多了解对面试还是有好处的。
数据类 data class
- data class的定义:
在类的前面加上data,很多人喜欢把它和JavaBean进行同等,但实际上差别很大。
下面会有一张图详细解释
data class Book(
val id:Long,
val name :String
){} // 通常{}不会使用,如果要复写equal toString方法的话,重新定义普通类是最好的选择。
对于data class 字段的属性只有Field,没有getter和setter
2.数据解构,component。
data class Book(
val id:Long,
val name :String
){}
fun function8(){
val book = Book(1L,"")
val id = book.component1() // 通过component1获取第一个值
val name = book.component2() // 通过component2获取第二个值
}
3.如果一定要把data class 作为java Bean使用,需要解决两个问题
①主构造器
想要添加主构造器,可以在module下的build.gradle中添加插件
id("org.jetbrains.kotlin.plugin.noarg") version "1.3.60"
然后创建一个接口注释类
再在module级别的build.gradle中写入noArg,位置一定要和上面保持一致
noArg {
invokeInitializers = true //为true的时候,会执行init块。默认为false不执行init块
annotations ("com.example.kotlintext.PoKo")
}
②去掉final才能被继承。目前的data类在kotlin中只能继承别人。
可以添加插件allopen,也是在module级别的build.gradle中添加:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.noarg") version "1.3.60"
id("org.jetbrains.kotlin.plugin.allopen") version "1.3.60"
}
noArg {
invokeInitializers = true
annotations ("com.example.kotlintext.PoKo")
}
allOpen {
annotations ("com.example.kotlintext.PoKo")
}
添加上后,就能够去掉生成data类的final标记,getter和setter也会去掉final标记。
常见的高阶函数
五个常见的高阶函数,let、run、also、apply、use。
①let函数,默认it作为参数传入,返回lambda表达式的return:
class Bi(){
var pocket = 0
}
fun main(){
val bi = Bi()
bi.let {
println(it.pocket)
}
}
②run函数,指定了receiver,返回lambda表达式的return:
class Bi(){
var pocket = 0
}
fun main(){
bi.run{
println(run.pocket)
}
}
③also函数,默认指定it,返回Unit:
class Bi(){
var pocket = 0
}
fun main(){
bi.also {
it.pocket = 5
println("also打印的结果是${it.pocket}")
}
}
④apply函数,指定了receiver,返回Unit:
class Bi(){
var pocket = 0
}
fun main(){
bi.apply {
this.pocket = 7
println("apply打印的结果是${this.pocket}")
}
}
⑤use函数,默认是it,返回的是lambda表达式的返回值
use函数可以自动关闭资源:
fun main() {
val file = File("example.txt")
// 使用 use 读取文件内容
file.bufferedReader().use { reader -> //默认是it,这里进行了更改为reader
val content = reader.readText()
println(content)
}
}
枚举类
- 枚举的定义
在class的前面加上关键字enum:
enum class State(){
}
- 枚举的属性
在枚举类中定义属性:
定义后,可以通过一些方法分别获取属性的值、序列号、所有内容
fun main(){
println("??/")
val r = State.Index1 //获取常量Index1
println(r)
val o = State.Index1.ordinal //获取Index1的序列
println(o)
val all = State.entries //获取枚举属性常量,类别是EnumEntries,也就是List;
println(all)
val question = State.values() //获取枚举常量,返回的是一个数组
println(question)
}
enum class State(){
Index1,Index2 //定义枚举的属性
}
上面标红的地方直接打印的话,无法打印出详细的枚举属性,需要转换成List就可以了。
3.定义构造器
4.枚举实现接口
对于枚举的属性而言,可以有共同的实现方法,或者单独的实现方法。
会优先检测单独的方法,如果没有的情况下,再用公共的实现方法。
例如:
enum class State(val id:Int):Runnable{
Index1(1){
override fun run() {
super.run() // Index1单独实现方法
}
},Index2(2);
override fun run() {
TODO("Not yet implemented") //公共实现方法
}
}
枚举类继承自Enum,不能继承其他类
5.枚举类扩展函数
下面的例子是把枚举类获取下一个元素:
fun State.next():State{ //State类就是上面定义的枚举类
return State.values().let {
val nextOrdinal = (ordinal+1)% it.size
it[nextOrdinal]
}
}
6.也可以定义枚举类的条件分支
例如:
fun query(s:String):Any {
val state = State.valueOf(s)
return when (state) {
State.Index1 -> { "" }
State.Index2 -> { 8 }
}
}
枚举类是有限个数,所以可以不加else。
7.枚举区间。
例如,我定义一个颜色枚举类,里面包含各种颜色。
我可以取一些颜色出来。:
enum class Color(){
White,Black,Red,Blue,Yellow //定义颜色的枚举
}
fun quColor(){
val colorRange = Color.White..Color.Black
val color = Color.Red
println(color in colorRange) //判断区间里面是否包含Red。
}
8.枚举的比较
fun main(){
if (state<= State.Index2){
println("Index1<=Index2")
//打印的结果是Index1<=Index2 因为比较的是ordinal序列号。
}
}
密封类 sealed class
1.密封类的定义
在class定义的前面加上sealed。
密封类sealed本质是一种特殊的抽象类。
密封的子类只能定义在与自身相同的文件中。
相比与枚举类,密封类的子类个数是有限的。
例如:
sealed class PlayState(){
}
2.密封类的构造器是私有的(即private)
如果改成public就会报错。
所以密封类在其他文件中是不可以被继承的。
3.密封类Vs枚举类
内联类 inline class
1.内联类的定义
在类定义的前面加上关键字inline
内联类需要有主构造器并且只有一个只读属性
inline class BoxString (val value: String){
}
2.内联类的属性
里面的属性不能有backing field,也就是,不能有属性的状态。
例如:
inline class BoxString (val value: String){
var age: Int
get() {
return 2
}
set(value) {
this.age = value
}
}
3.内联类的继承结果
内联类可以实现接口,但是不能继承父类(防止父类有backing field),也不能被继承(防止复杂化)
4.内联类的编译优化
内联类只有在必要的时候转换成包装类型。
例如:
inline class BoxString (val value: String){
}
fun ca(){
val box = BoxString("") //编译优先转换成String类型
val newBox = box.value * 200 // 使用String类型进行运算
println(newBox) //这里会转换成包装类型
}
5.内联类模拟枚举
通常性能优化的时候,会考虑用内联类,因为内联类相比与枚举消耗的内存更少,运行更加顺畅。
6.内联类的限制
Moshi的序列化和反序列化
1.添加插件
2.添加depend依赖
3.moshi的序列化和反序列化
data class UserData(val count: String,val password:String){
}
fun main(){
//moshi的序列化和反序列化
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(UserData::class.java)
val userD= UserData("Aiden","asd790831")
println(jsonAdapter.toJson(userD)) //moshi的序列化
val json = """{"count":"An","password":"asd791125"}"""
println(jsonAdapter.fromJson(json)) //moshi的反序列化
}
Gson的序列化和反序列化
Gson的序列化,是把kotlin对象转换成json或者xml格式。
反序列化则是把json或xml格式转换成对象。
序列化和反序列化有什么好处:
- 数据保存得更加久,因为用了序列化可以把对象的状态保存在文件中
- 在网络传输的时候把对象序列化为json或者XML利于网络传输。
- 不同的平台或者不同的编程语言的数据交换,需要使用通用格式,如json或xml。
- 远程调用,在分布式系统中,远程方法调用(RPC)或消息传递系统中,通常需要将参数对象序列化,以便发送给远程系统。反序列化则可以还原收到的对象,进行进一步处理。
- 简化数据传递:在Android开发中,Activity之间的数据传递、Bundle或Intent中传递复杂对象时,通常需要将对象序列化为可传递的格式,如Parcel。
使用Gson需要下面几个步骤:
①在文件的bulid.gradle中添加依赖
implementation ("com.google.code.gson:gson:2.8.9")
②使用Gson
data class UserData(val count: String,val password:String){
}
fun main(){
val gson = Gson()
val userData = UserData("An","asd791125")
val serialization = gson.toJson(userData) //把对象转换成json字符串,序列化
println(serialization) //转换成序列化
val json = """{"count":"An","password":"asd791125"}"""
//把json转换成对象,反序列化
val Deserialization = gson.fromJson(json,UserData::class.java)
println(Deserialization)
}
③Gson默认不会序列化为null的字段,如果要包含null,可以使用GsonBuilder配置
例如:
val gNull = GsonBuilder().serializeNulls().create() //创建一个包含Null的Gson
④在kotlin中使用默认值,然而在Gson反序列时不会使用这些默认值。解决方法可以使用@SerializedName或者自定义反序列化
⑤如何自定义序列化和反序列化
fun main(){
val cGson = GsonBuilder() //创建一个GsonBuilder对象,用于自定义Gson
.registerTypeAdapter(
Date::class.java, //这行告诉Gson,只有遇到Date的时候需要使用自定义序列化
Serializers.DateSerializer
)
.registerTypeAdapter(
Date::class.java,
Serializers.DateDeserializer
)
.create()
val cPersonDate = PersonDate("Aiden","female",Date())
println(cGson.toJson(cPersonDate)) //自定义序列化
println(cGson.fromJson("""{"name":"An","gender":"male","brithDay":"2024-08-15 22:45"}""",PersonDate::class.java))
}
//定义一个PersonData数据类,储存个人数据信息。birthDay信息是Date类
data class PersonDate(val name :String,val gender:String,val brithDay:Date)
//自定义序列化Date类型的数据
object Serializers{
//定义df常量,用于格式化日期
private val df:DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm")
object DateDeserializer:JsonDeserializer<Date>{ //采用单例模式,存放序列化和反序列化
override fun deserialize( // 复写反序列化
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Date {
return df.parse(json?.asString
?:throw Exception("Json element is null")) //空类型安全,用elvis表达式,如果会空就throw Exception
}
}
object DateSerializer: JsonSerializer<Date>{
override fun serialize( //复写序列化
src: Date?,
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return JsonPrimitive(df.format(src))
}
}
}
KotlinX Serialization的序列化和反序列化
1.添加插件:
1.9.22 是我的版本号,你可以改成自己的版本号。
版本号的查看,在tools->kotlin->configure kotlin plugin updates
2.添加依赖
3.使用序列化和反序列
记得给数据类添加注解:
@Serializable
data class Count (val name: String,val age :Int){
}
fun main(){
//kotlinX .serialization 的序列化和反序列化
val count = Count("",5)
println(Json.encodeToString(count)) //kotlinX的序列化
val dCount = """{"name":"","age":}"""
println(Json.decodeFromString<Count>(dCount)) //kotlinX的反序列化
}
}
泛型的基本概念
- 泛型的定义。
用于在编写类、接口、方法或函数时,使其能够处理不同类型的数据,而不必明确指定具体的数据类型。泛型通过引入类型参数来实现这一点,使代码更加灵活和可复用,同时还可以提供类型安全。
①如果是在函数定义成泛型
在fun后面<T>
fun <T> type(a:T){
}
②类申明为泛型
在类名的后面加<T>
class Type<T>(){
}
2.泛型形参和实参
class Box<T>(var value: T)
fun main() {
val intBox = Box(123) // T 被推断为 Int
val stringBox = Box("Hello") // T 被推断为 String println(intBox.value) // 输出 123
println(stringBox.value) // 输出 Hello
}
泛型约束
1.添加类型约束
fun <T:Comparable<T>> compare(a:T,b:T):T{
return if (a>b) a else b //约束T实现Comparable T
}
2.实现多个约束
fun <T> call (a:T,b:T)
where T:Comparable<T>,T:() -> Unit{
if (a>b) a()else b()
}
T约束为Comparable,和参数为空,返回为Unit的函数类型。
3.多个泛型参数
fun <T,R> callMax(a:T,b:T):R //传入泛型T和R R还作为返回值
where T:Comparable<T>,T:()->R, //实现Comparable 接口,并且必须是一个函数类型
R:Number{ // R必须是Number的子类
return if (a>b )a() else b()
}
泛型中违反型变约束
有时候,申明协变,即加上关键字out,应该是作为生产者 有返回值T。
但是出现了逆变点(在参数中)。
有下面几种情况,都是违反型变约束:
- 申明为协变(out),出现逆变点(参数中)
- 申明为逆变(in),出现协变点(返回值)
避免不了这种情况出现的时候,怎么使用。
可以在前面加上@UnsafeVariance,代表让编译器忽略,我们自己处理
保证安全的前提:
1.泛型参数协变,逆变点不能引起修改,即只读不写
2.泛型参数逆变,协变点不得外部获取,即只写不读
泛型的型变
泛型的型变分为三种。
不变、协变、逆变。
1.不变
默认泛型是不变的。即使两个类型之间存在子类型关系,泛型版本是没有任何关系的。
比如Int 和Number,Int是Number的子类:
class Box<T> (val value:T){
}
fun useBox(box:Box<Number>){
}
fun iu(){
val intBox = Box(1) //进行类型推导intBox类型为Box<Int>+
useBox(intBox) //会报错,需要的类型为Number,Int不可以。
}
2.协变
协变作为生产者,使用于返回值的场景
协变允许你使用一个泛型类型的子类型作为另一个泛型类型的参数。
使用out关键字来申明。
interface Book1
interface FaceBook:Book1
class BookStore<out T:Book1>{ //out 关键字
fun getBook():T{ //这里的T,就是协变点
TODO()
}
}
fun foundBook(){
val fBook = BookStore<FaceBook>()
val book1:BookStore<Book1> = fBook //向上兼容
val fbook2:BookStore<FaceBook> =book1 //无法向下转,会报错。
val book2 = book1.getBook()
val book3:FaceBook = book1.getBook() //getBook返回的是Book1,类型不匹配会报错。
}
协变可以向上兼容,不能向下
3.逆变
逆变作为消费者,适用于输入参数的场景
函数的参数类型为T,可以定义为逆变点。
使用关键字in来申明
逆变允许泛型类型的父类型传递给泛型的子类型
open class Garbage
class DryGar: Garbage()
class DustBin<in T:Garbage>{
fun putGar(t:T){
}
}
fun findGarbage(){
val dustBin = DustBin<Garbage>() //逆变之后dustBin成为了子类
val dryGar:DustBin<DryGar> = dustBin //可以向下兼容
val d:DustBin<Garbage> =dryGar //无法向上兼容
val instanceG = Garbage()
val instanceD = DryGar()
dustBin.putGar(instanceG) //子类可以替换所有的父类,所以没问题
dustBin.putGar(instanceD)
dryGar.putGar(instanceD)
dryGar.putGar(instanceG ) //父类的dryGar不能传递给子类,所以需要类型是DryGar().
}
泛型中的星投影
星投影是一种处理泛型类型不确定或不关心类型参数的使用场景。
主要的应用场景有以下两点:
①忽略类型参数:
当你不关心泛型类型参数是什么类型的时候,可以使用星投影表示任何类型
②处理未知类型:
当你不知道泛型类型参数的具体类型的时候,可以使用星投影。
- 无限制星投影
假如你有一个泛型类,使用Box<*>可以安全读取Box的内容,无法写入。
无法写入是因为很多能导致类型不匹配出错。(除非写入的类型是null)
class Box<T>(val value: T)
fun printBox(box: Box<*>) {
val value = box.value
println(value) // value 的类型为 Any?,可以安全地读取
}
val intBox: Box<Int> = Box(42)
val stringBox: Box<String> = Box("Hello")
printBox(intBox) // 输出 42
printBox(stringBox) // 输出 Hello
2.协变星投影
协变类型有一个上限类型,上限类型为定义的泛型的约束类型。
对于协变类型参数(声明为 out T),Box<out T> 的星投影类似于 Box<out Any?>,即你可以读取值,但不能写入值(除了 null)。
不能写入的原因也是因为很可能造成类型不匹配。
interface Source<out T> {
fun nextT(): T
}
fun useSource(source: Source<*>) {
val value = source.nextT()
println(value) // value 的类型为 Any?
}
3.逆变星投影
逆变类型有个下限,下限类型为nothing
对于逆变类型参数(声明为 in T),Box<in T> 的星投影类似于 Box<in Nothing>,即你可以写入 null,但无法安全读取。
interface Consumer<in T> {
fun consume(t: T)
}
fun useConsumer(consumer: Consumer<*>) {
// 无法向 consumer 传递任何非 null 的值,因为类型不确定
// consumer.consume(Any()) // 错误
consumer.consume(null) // 可以传递 null
}
星投影的使用范围:
①不能直接或者间接应用在属性或者函数上。
QueryMap<String,*>() 或 maxOf<*>(1,3)
②可以使用在类型描述的场景。
Val queryMap:QueryMap<*,*>
③特殊在类型构造中,泛型参数中还需要一个泛型参数,可以使用*
HashMap<String,List<*>>()
泛型的类型擦除和内联特化
Kotlin和java中的泛型实际上是伪泛型,在编译时会擦除类型。
运行时没有实际类型生成。
例如:
fun <T> type(t:T) {
val jcalss = T:class.java //编译时类型擦除了
}
可以使用内联特化解决这个问题。
因为内联特化,可以在使用的时候,才运行里面的代码。
这样泛型就会有具体的类型
例如:
inline fun <reified T> t(t:T) {
val jcalss = T::class.java //调用的时候,T会变成实际的类
}
内联特化实际应用:
可以简化Gson传输的时候只用传入Json,省略掉反射部分。
反射的类型,可以用类型推导或者是泛型参数。
模仿Scala中的self Type
//利用泛型 模拟Scala中的Self Type
typealias OnConfirm = ()->Unit
typealias OnCancel = ()->Unit
private val EmptyFunction={}
open class Notification(
val title:String,
val content:String){
}
class ConfirmNotification(
title: String,
content:String,
val onConfirm: OnConfirm,
val onCancel: OnCancel
):Notification(title, content)
interface SelfType<Self>{
val self:Self
get() = this as Self
}
open class NotificationBuilder<Self:NotificationBuilder<Self>>:SelfType<Self> {
//这里把泛型Self的类型加以限制为NotificationBuilder<Self>,这样只能传入其子类,保证类型安全。
protected var title: String = ""
protected var content:String = ""
fun title(title: String):Self{
this.title = title
return self
}
fun content(content: String):Self{
this.content = content
return self
}
open fun build() = Notification(this.title,this.content)
}
class ConfirmNotificationBuilder:NotificationBuilder<ConfirmNotificationBuilder>(){
private var onConfirm:OnConfirm = EmptyFunction
private var onCancel:OnCancel = EmptyFunction
fun onConfirm(onConfirm: OnConfirm):ConfirmNotificationBuilder{
this.onConfirm = onConfirm
return this
}
fun onCancel(onCancel: OnCancel):ConfirmNotificationBuilder{
this.onCancel = onCancel
return this
}
override fun build(): ConfirmNotification {
return ConfirmNotification(title,content,onConfirm,onCancel)
}
}
fun main() {
ConfirmNotificationBuilder()
.title("Hello")
.onCancel {
println("onCancel")
}.content("World")
.onConfirm {
println("onConfirmed")
}
.build()
.onConfirm()
}
简化View model和model的交流
通过对模型管理的自动化、类型安全的依赖注入、以及线程安全的操作.
简化了应用中model与 ViewModel 的交互,提升了代码的可读性和可维护性。
例如:
object Models{
//ConcurrentHashMap建立键值对,键是class::,值是AbsModel.
private val modelMap = ConcurrentHashMap<Class<out AbsModel>,AbsModel>()
//this.java是KClass<T>的扩展属性,用于于KClass关联的java Class对象
fun AbsModel.register(){
modelMap[this.javaClass]= this
}
//this.javaClass是任何kotlin对象的扩展方法,用于获取java Class对象
fun <T:AbsModel> KClass<T>.get():T{
return modelMap[this.java] as T
}
}
/*
定义所有Model的基类,每个子类实现的时候,都会执行init中的注册功能.
ConcurrentHashMap 是一种适用于高并发环境的线程安全映射数据结构。
它通过分段锁定和无锁操作等机制,实现了在多线程访问时的高效性和一致性。
*/
abstract class AbsModel{
init {
Models.run {
register()
}
}
}
class DatabaseModel:AbsModel(){
fun query(sql:String):Int=0
}
class NetworkModel:AbsModel(){
fun get(url:String):String="""{"code":0}"""
}
/*
ReadOnlyProperty是用于val,只读属性。
<R,T>需要传入两个参数,第一个参数是持有者,第二个参数是输出类型。
*/
class ModelDelegate<T:AbsModel>(val Kclass:KClass<T>):ReadOnlyProperty<Any,T>{
//KClass是kotlin中的class,和java中的class相比,作用域不同。
override fun getValue(thisRef: Any, property: KProperty<*>): T {
return Models.run { Kclass.get() }
}
}
//初始化model
fun initModels(){
DatabaseModel()
NetworkModel()
}
/*
简化拿到类型反射。
使用内联特化防止类型擦除。
*/
inline fun <reified T:AbsModel> modelOf():ModelDelegate<T>{
return ModelDelegate(T::class)
}
class MainViewModel{
val databaseModel by modelOf<DatabaseModel>()
val networkModel by modelOf<NetworkModel>()
}
fun main(){
initModels()
val mainActivity = MainViewModel()
/*这里的let(::println),相当于
mainActivity.databaseModel.query("").let({
println(it)
})*/
mainActivity.databaseModel.query("select * from mysql.user").let(::println)
mainActivity.networkModel.get("https://wwww...").let(::println)
}
恭喜你,学到这里,你已经掌握了kotlin的百分之80的知识点和用法了!!!
只需再学习kotlin(下),就能把所有的知识学完了。