Kotlin基础&结合Spring

zhiyu1998...大约 18 分钟Kotlin

基础知识

基础类型

Java中基本类型声明

int a = 2;
final String b = "Hello Java"

对照Kotlin中的声明:

var a:Int = 2
val b:String = "Hello Kotlin"

long类型

Java

long c = 12345678910l;
long d = 12345678910L;

Kotlin

val c = 12345678910l // error
val d = 12345678910L // fine

数值转换

Java

int e = 10;
long f = e;

Kotlin

val e:Int = 10
val f:Long = e.toLong()

无符号类型

Java没有,但是kotlin有

image-20220809110526087
image-20220809110526087

字符串模板

fun main() {
    val j = "I Love China"
    println("Value of String j is: $j")
}

比较运算

在Java中比较引用,equals()比较内容,而在Kotlin中=比较的是引用,==比较的是内容

System.out.println(k==m);
System.out.println(k.equals(m));
println(k===m)
println(k==m)

数组

image-20220809171520308
image-20220809171520308

数组的创建

Java

int[] c = new int[]{1,2,3,4,5};

Kotlin

val c0 = intArrayOf(1,2,3,4,5)
val c1 = IntArray(5){it + 1} 

数组的长度

Java中使用length来计算

int[] a = new int[5];
System.out.println()

Kotlin则使用size

val a = IntArray(5)
println(a.size)

数组的遍历

Java

float[] e = new float[]{1,3,5,7};
for (float element : e) {
    System.out.println(element);
}

Kotlin

val e = floatArrayOf(1f, 3f, 5f, 7f)
for (element in e) {
    println(element)
}
-----------or--------------
e.forEach { element -> println(element) }

数组的包含关系

Java中

for (float element:e) {
    if (element == 1f) {
        System.out.println("1f exists in variale 'e'");
        break;
    }
}

Kotlin

if (1f in e) {
    println("1f exists in variale 'e'")
}

区间

..符号

val intRange = 1..10
/*for (i in intRange) {
        println(i)
    }*/
val charRange = 'a'..'z'
val longRange = 1L..10L
val floatRange = 1f..10f
val doubleRange = 1.0..2.0
println(intRange.joinToString())
println(charRange.joinToString())
==================================
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z

步距step

val intRangeStep = 1..10 step 2
val charRangeStep = 'a'..'z' step 2
println(intRangeStep.joinToString())
println(charRangeStep.joinToString())
====================================
1, 3, 5, 7, 9
a, c, e, g, i, k, m, o, q, s, u, w, y

until

until和..的区别在于,它是右闭区间

val intRangeUtil = 1 until 10
val charRangeUtil = 'a' until 'z'
println(intRangeUtil.joinToString())
println(charRangeUtil.joinToString())
=====================================
1, 2, 3, 4, 5, 6, 7, 8, 9
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y

倒数:downTo

val intRangeDownto = 10 downTo 1
val charRangeDownto = 'z' downTo 'a'
println(intRangeDownto.joinToString())
println(charRangeDownto.joinToString())

区间的遍历

在Java中遍历数组是可以是这样

int[] array = new int[]{1,3,5,7};
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}

在kotlin中

val array = intArrayOf(1,3,5,7)
for (i in 0 until array.size) {
    println(array[i])
}

更加简化的版本是

for(i in array.indices) {
    println(i)
}

集合

image-20220810163454521
image-20220810163454521

List

Java中创建ArrayList

List<Integer> intList = new ArrayList<>(Arrays.asList(1,2,3));

Kotlin中则分为可变List和不可变

val intList: List<Int> = listOf(1, 2, 3)
val intList2: MutableList<Int> = mutableListOf(1, 2, 3)

如果想像Java一样创建集合则

val stringList = ArrayList<String>()

那么它是如何实现创建集合和Java一样的呢?类型别名

image-20220810194304356
image-20220810194304356
增加元素/删除元素

Java中的添加元素

ArrayList<String> stringList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    stringList.append("num: " + i);
}

kotlin的添加元素

val stringList = ArrayList<String>()
for (i in 1..10) {
    stringList += "num: $i"
}

Kotlin中的删除元素则是

for (i in 1..10) {
    stringList -= "num: $i"
}
读写元素

Java中

stringList.set(5, "HelloWorld");
String valueAt5 = stringList.get(5);

Kotlin

stringList[5] = "HelloWorld"
val valueAt5 = stringList[5]

map

Java中读写元素

HashMap <String, Integer> map = new HashMap<>();
map.put("Hello", 10);
System. out.println(map.get("Hello"));

Kotlin

val map = HashMap<String, Int>()
map["Hello"] = 10
println(map["Hello"])

pair

如何创建?

val pair = "Hello" to "Kotlin"
val pair2 = Pair("Hello", "Kotlin")

访问对应元素

val first = pair.first
val second = pair.second

对象解构

val (x, y) = pair

函数

结构如下:

image-20220810235032740
image-20220810235032740

函数引用

这个和Java8的函数应用一致

符号是:::

像是定义了一个函数

class Foo {
    fun bar() {
        
    }
}

如果要使用它的引用Foo::bar

变长参数

fun test1(vararg args: Int) {
    println(args.contentToString())
}
fun main() {
    test1(11,22,33)
}
========================================
[11, 22, 33]

多返回值

定义返回一个Triple

fun multiRetureValues(): Triple<Int, Long, Double> {
    return Triple(1, 3L, 4.0)
}

使用的时候就可以使用对象解构

val (a, b, c) = multiRetureValues()

默认参数

fun defaultParameter(x: Int = 5, y: String, z: Long = 0L) {
    println("$x,$y,$z")
}

同时,Kotlin支持具名参数

defaultParameter(y = "Hello")

类 & 接口

Java中定义类中的成员遍历不需要赋值

public class SimpleClass{
    public int x;
    public void y(){}
}

Kotlin中必须给成员变量赋值

class SimpleClass{
    var x: Int = 0
    fun y(){}
}

构造方法

Java

public class SimpleClass {
    public int x;
    public SimpleClass(int x) {
    	this.x = x;
    }
    ..
}

Kotlin,但是下面的风格不适合Kotlin

class SimpleClass{
    var x: Int
    constructor(x:Int){
        this.x = x
    }
    ..
}

下面这个风格才是Kotlin

class SimpleClass
constructor(x: Int) {
    var x:Int = x
}

还可以再进一步简化

class SimpleClass(x: Int) {
    
}

类的实例化

Java

SimpleClass simpleClass = new SimpleClass(9);
System.outprintln(simpleClass.x);
simpleClass.y();

Kotlin,不需要new

val simpleClass = SimpleClass(9)
println(simpleClass.x)
simpleClass.y()

接口

Java中的接口

public interface SimpleInf {
void simpleMethod();
}

Kotlin中的接口(不需要写public,默认)

interface Simplelnf {
fun simpleMethod()
}

实现接口

Java中实现接口的方法

public class SimpleClass implements SimpleInf {
    @Override
    public void simpleMethod() {
    }
}

Kotlin(使用override)

class SimpleClass(var x: Int): SimpleInf {
override fun simpleMethod() {
}

抽象类定义

Java

public abstract class AbsClass {
    public abstract void absMethod();
    protected void overridable(){ }
    public final void nonOverridable(){ }
}

Kotlin(默认不可以覆写,只有通过open才能被覆写)

abstract class AbsClass {
    abstract fun absMethod()
    open fun overridable(){}
    fun nonOverridable(){}
}

类的继承

Java

public class SimpleClassextends AbsClass implements Simplelnf {
..
}

Kotlin(注意继承类的时候加上括号,表示调用父类的构造方法

class SimpleClass(var x:Int) : AbsClass(), SimpleInf{
    ...
}

属性引用

这个基本和函数引用差不多

class Person(var name: String, var age: Int) {}

val ageRef = Person::age
val nameRef = person::name
val person = Person(18, "Bennyhuo")
ageRef.set(person, 20)
nameRef.set("Andyhuo")
=======================================
Andyhuo
20

空类型安全

在类的章节中已经介绍了,如果Kotlin中声明了一个成员变量,就必须给变量赋值,那么怎么为空呢?就是加一个?

var nullable: String? = "Hello"
nullable = null

如果加了?那么在某些情况就会编译错误

var length = nullable.length

【不推荐】那么也可以强制使用,就是使用!!,使用的时候要知道它肯定有值

var length = nullable!!.length

那么也可以安全访问,使用?

var length = nullable?.length

也可以使用elvis运算符 ?:

var length = nullable?.length ?: 0

父子替换

父类可以替换为子类,但是反过来不行(Number是Int的父类)小的可以替换大的

var x: String = "Hello'
var y: String? = "World"
x=y // Type mismatch
y=x// OK
var a:Int = 2
var b: Number = 10.0
a=b // Type mismatch
b=a//OK

智能类型转换

例如下面的这段代码:

val kotliner: Kotliner = Person("benny", 20)
if(kotliner is Person){
	println((kotliner as Person).name)
}

Kotlin的智能转换可以换成下面,Kotliner自动转换成Person

val kotliner: Kotliner = Person("benny", 20)
if(kotliner is Person){
	println(kotliner.name)
}

空类型的自动转换

  • 在if之前是是String?
  • if中:String
  • if后:String?
var value: String? = null
value = "benny"
if(value != nulI){ t
	println(value.length)
}

表达式

分支表达式

if..else区别

Java

c = a == 3 ? 4 : 5;

Kotlin

c = if (a == 3) 4 else 5
switch & when

Java中的switch

switch(a) {
    case 0 :
        c = 5; break;
    case 1 :
        c = 100; break;
    default :
        c = 20
}

Kotlin中的when

when(a) {
    0 -> c = 5
    1 -> c = 100
    else -> c = 20
}

Kotlin中when的智能转换(如果x是字符串,则在x.length自动转换成字符串)

var x: Any = ...
when {
    x is String -> c = x.length
    x == 1 -> c = 100
    else c = 20
}

捕获异常

Java和Kotlin都一致

try {
    c = a / b
} catch (e: Exception) {
    e.printStackTrace()
    c = 0
}

但是Kotlin可以进一步写为

c = try {
    a / b
} catch (e: Exception) {
    e.printStackTrace()
    c = 0
}

中缀表达式

infix 定义了Kotlin的简写

infix函数(中缀方法)需要几个条件:

  • 只有一个参数
  • 在方法前必须加infix关键字
  • 必须是成员方法或者扩展方法
fun main() {
    println("HelloWorld" rotate 5)
}

infix fun String.rotate(count: Int): String {
    val index = count % length
    return this.substring(index) + this.substring(0, index)
}
==============================================================
WorldHello

再例如

class Book
class Desk

infix fun Book.on(desk: Desk) {
    
}

fun main() {
    val book = Book()
    val desk = Desk()
    book on desk // 具体用法
}

Lambda表达式

匿名函数

func是变量名

val func = fun() {
    println("Hello")
}

func() // 调用
匿名函数的类型
val func: () -> Unit = fun() {
    println("Hello")
}
Lambda表达式

Java

// Jdk 8
Runnable lambda = () -> {
    ....
}
// Jdk 10以上
var lambda = () -> {
    ....
}

Kotlin

val lambda = {
    println("Hello")
}
// 有返回参数的
val lambda: () -> Unit = {
    println("Hello")
}

表达式参数类型从表达式类型推断出来

val f1: (Int) -> Unit = {
        p: Int -> println(p)
    }
val f1: Function1<Int, Unit> = {
        p -> println(p)
    }

表达式从声明推断而来

val f1 = {
        p: Int -> println(p)
    }

表达式的最后一行表示返回值

val f1 = {
        p: Int -> println(p)
        "Hello"
    }

另外还有省略参数的形式

val f1: Function1<Int, Unit> = {
        println(it)
        "Hello"
    }

自己实现hashcode注意问题

需要注意的是:自己实现hashcode如果某个变量发生了变化要移除是移除不了的,除非定义构造函数的时候使用val而不是var

class Person(var name: String, var age: Int) {
    override fun equals(other: Any?): Boolean {
        val other = other as? Person ?: return false
        return other.age == age && other.name == name
    }

    override fun hashCode(): Int {
        return 1 + 7 * age + 13 * name.hashCode()
    }
}

fun main() {
    val persons = HashSet<Person>()

//    (0..5).forEach{
    val person = Person("Benny", 20)
    persons += person
//    }

    println(persons.size)
    person.age++ // 变化

    persons -= person // 移除不了

    println(persons.size)
}
===========================================
1
1

高阶函数

内联函数

  • public/protected的内联方法只能访问对应类的public成员
  • 内联函数的内联函数参数不能被存储(赋值给变量)
  • 内联函数的内联函数参数只能传递给其他内联函数参数
fun cost(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("${System.currentTimeMillis() - start}ms")
}

fun main() {
    cost {
        println("Hello")
    }
}

上述的写法相当于,如果不加inline则就是一个Lambda表达式

fun main() {
    val start = System.currentTimeMillis()
    println("Hello")
    println("${System.currentTimeMillis() - start}ms")
}

non-local return

定义

inline fun nonLocalReturen(block: ()->Unit) {
    
}

nonLocalReturn{
    return
}

如何禁止? crossinline

几个有用的高阶函数

image-20220919172117338
image-20220919172117338

集合遍历

filter操作

Java

list.stream().filter(e -> e % 2 == 0)

Kotlin

list.filter { it % 2 == 0 }

Kotlin还可以转换为懒序列

list.asSequence().filter { it % 2 == 0 }
map

Java

list.stream().map(e -> e * 2 + 1)

Kotlin

list.filter { it * 2 + 1 }
flatMap
image-20220919174842822
image-20220919174842822

Java

var list = new ArrayList<Integer>();
list.addAll(Arrays.asList(1, 2, 3, 4));
list.stream().flatMap(e -> {
    var list1 = new ArrayList<Integer>(e);
    for (int i = 0; i < e; i++) {
        list1.add(i);
    }
    return list1.stream();
}).collect(Collectors.toList()).forEach(System.out::print);

Kotlin

list.flatMap { 0 until it }.forEach(::print)
fold
image-20220919175051480
image-20220919175051480

SAM转换

Java

  • 一个参数类型为只有一个方法的接口的方法调用时可用Lambda表达式做转换作为参数

Kotlin

  • 一个参数类型为只有一个方法的Java接口的Java方法调用时可用Lambda表达式做转换作为参数
image-20220920091657937
image-20220920091657937

类的进阶

构造器

构造器的基本写法

前一个类内全局可见,name构造器可见(init块,属性初始化)

class Person(var age: Int, name: String)

init块

init块可以有多个

class Person(var age: Int, name: String) {
    var name: String
    init {
        this.name = name
    }
    var firstName = name.split(",")[0]
    init {
        
    }
}

继承及调用副构造器

class Person(var age: Int, name: String) : Animals() {
    constructor(age:Int):this(age, "unknown")
    // 定义了主构造器后在类内部
    // 再定义构造器都称为副构造器
}

默认构造器

class Person(var age: Int, var name = "unknown") : Animals()

如果要让Java以重载的方式调用可以加上注解@JvmOverLoads


class Person
@JvmOverLoads
constructor(var age: Int, var name = "unknown") : Animals()

类成员的可见性

default & internal

  • 一般由SDK或公共组件开发者用于隐藏模块内部细节实现
  • default可通过外部创建相同包名来访问,访问控制非常弱
  • default会导致不同抽象层次的类聚集到相同包之下
  • internal可方便处理内外隔离,提升模块代码内聚减少接口暴露
  • internal修饰的Kotlin类或成员在Java当中可直接访问

延迟初始化

使用null

如果非要初始化(像是安卓里的TextView)那么有以下解决方案

private var nameView? = null

lateinit

private lateinit var nameView: TextView

判断lateinit如何初始化

if(::nameView.isInitialized) {}
  • lateinit会让编译器忽略变量的初始化,不支持Int等基本类型
  • 开发者必须能够在完全确定变量值的生命周期下使用lateinit
  • 不要在复杂的逻辑中使用lateinit,它只会让你的代码更加脆弱
  • Kotlin 1.2加入的判断lateinit属性是否初始化的API最好不要用

【推荐】lazy

private val nameView by lazy {
}

代理

接口代理

interface Api {
    fun a()
	fun b()
	fun c()
}
-------------
class ApiImpl : Api {
    override fun a() {}
    override fun b() {}
    override fun c() {}
}

使用接口代理(日志、埋点)

class ApiWrapper(val api: Api) 
: Api by api {
    override fun c() {
        println("c is called")
        api.c()
    }
}

单例模式

饿汉式在Java中是体现在new,类加载时实例化对象Singleton

class Singleton {
    public static final Singleton INSTANCE = new Singleton();
}

Kotlin

object Singleton {}

如何访问object成员呢?

kotlin

object Singleton {
    var x: Int = 2
    fun y(){}
}

Singleton.x
Singleton.y()

Java

Singleton.INSTANCE.getX();
Singleton.INSTANCE.setX();
Singleton.INSTANCE.y();

静态成员 @JvmStatic

这样声明之后是静态的,但是还是需要getter/setter

object Singleton {
    @JvmStatic var x: Int = 2
    @JvmStatic fun y(){}
}

如果使用@JvmField,在被Java调用的时候就可以不用getter/setter

object Singleton {
    @JvmField var x: Int = 2
    @JvmStatic fun y(){}
}

伴生对象

class Foo {
    companion object {
        @JvmStatic fun y(){}
    }
}

在Java中是这样

public class Foo {
    public static void y(){}
}

内部类

Java

class Outer {
    class Inner {}
    static class StaticInner{}
}

Kotlin

class Outer {
    inner class Inner
    class StaticInner
}

内部object

object OuterObject{
    object StaticInnerObject
}

匿名内部类

容易造成泄露,因为它定义在非静态区域

Java

new Runnable() {
    @Override
    public void run() {
        
    }
}

Kotlin,可以看到object后直接忽略了名字

object: Runnable{
    override fun run() {
        
    }
}

Java中不支持实现多个接口的匿名内部类,但是Kotlin支持

object: Cloneable, Runnable{
    override fun run() {
        
    }
}

数据类

定义之前加一个data

data class Book(val id: Long,
               val name: String,
               val author: String)

比较Java可以看到这个在干嘛(@Data来源于Lombok)

@Data
public class Book {
    private long id;
    private String name;
    private Person person;
}

数据类解构

fun main() {
    val pair = "Hello" to "World"
    val (hello, world) = pair
}
//结构原理
data class Pair<out A, out B> (
    public val first: A,
    public val second: B
):java.io.Serializable

Java Bean & data class

image-20220924133546730
image-20220924133546730

JSON序列化示例

Gson
data class Person(val name:String, val age:Int)

fun main() {
    var str = """
        {
    "name": "请求成功",
    "age": "18"
}
    """.trimIndent()
    
    println(Gson().toJson(Person("Zhiyu 1998", 20)))
    println(Gson().fromJson(str, Person::class.java))
}
Moshi

导包:

implementation("com.squareup.moshi:moshi-kotlin:1.14.0")

使用

val moshi: Moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()
val jsonAdapter: JsonAdapter<Person> = moshi.adapter(Person::class.java)
println(jsonAdapter.toJson(Person("Zhiyu", 20)))
println(jsonAdapter.fromJson(
    """
        {
            "name": "zhiyu",
            "age": "199999"
        }
    """.trimIndent()
))
Kotlinx

导包注意项:

plugins {
    kotlin("jvm") version "1.7.10"
    kotlin("plugin.serialization") version "1.7.10"
}

dependencies {
    ...
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
}

就可以编写了

@Serializable
data class Person(var name:String, var age:Int) {
    init {
        if (name.isNullOrBlank()){
            name="kotlinx.serialization";
            age=12
        }
    }
}

fun main() {
    println(
        Json.decodeFromString<Person>(
            """
                {"name":"kotlinx.serialization","age":12}
            """
        )
    )
}

枚举类

Java

enum State {
    Idle, Busy
}
State.Idle.name(); // Idle
State.Idle.ordinal(); // 0

Kotlin

enum class State{
    Idle, Busy
}
State.Idle.name // Idle
State.Idle.ordinal // 0

定义构造器

enum State{
    Idle(0), Busy(1);
    int id;
    State(int id) {
        this.id = id;
    }
}

Kotlin

enum class State(val id:Int) {
    Idle(0), Busy(1)
}

枚举定义扩展

fun State.next(): State {
    return State.values.let {
        val nextOrdinal = (ordinal + 1) % it.size
        it[nextOrdinal]
    }
}

密封类

  • 密封类是一种特殊的抽象类
  • 密封类的子类定义在与自身相同的文件中
  • 密封类的子类的个数是有限的
sealed class PlayerState {
    constructor()
    
    constructor(int: Int)
}

密封类&枚举类区别

image-20220924135345615
image-20220924135345615

内联类

  • 内联类是对某一个类型的包装
  • 内联类是类似于Java装箱类型的一种类型
  • 编译器会尽可能使用被包装的类型进行优化

定义示例:

inline class BoxInt(val value: Int)

内联类的方法

inline class BoxInt(val value: Int) {
    operator fun inc(): BoxInt {
        return BoxInt(value + 1)
    }
}

【错误设想】内联类的属性

这样会报错:inline class cannot have properties with backing field

inline class BoxInt(val value: Int) {
    val name = "BoxInt($value)"
}

内联类的继承结构

内联类可以实现接口,但不能继承父类也不能被继承

inline class BoxInt(val value: Int): Comparable<Int> {
    override fun compareTo(other: Int) = value.compareTo(other)
}

编译优化

var boxInt = BoxInt(5)
if (boxInt < 10) {
    println("value is less than 10")
}

编译优化成Int类型: var value: Int = 5

使用Int类型: val newValue = value * 200

方法编译成静态方法: BoxInt.inc-impl(value)

必要时使用包装类型: println(BoxInt(value))

var boxInt = BoxInt(5)
val newValue = BoxInt.value * 200
println(newValue)
boxInt++
println(boxInt)

使用场景

无符号类型
inline class UInt
internal constructor(internal val data:Int): Comparable<UInt> {
   
}
模拟枚举
inline class State(val ordinal: Int) {
    companion object {
        val Idle = State(0)
        val Busy = State(1)
    }
    fun values = arrayOf(Idle, Busy)
    val name: String
    	get() = ...
}

限制

  • 主构造器必须有且仅有一个只读属性
  • 不能定义有backing-field 的其他属性
  • 被包装类型必须不能是泛型类型
  • 不能继承父类也不能被继承
  • 不能定义为其他类的内部类

对比typealias

image-20220924141434215
image-20220924141434215

泛型

以maxOf看一下泛型:

public actual fun <T : Comparable<T>> maxOf(a: T, b: T): T {
    return if (a >= b) a else b
}

再看一下Java

public static <T extends Comparable<T>> T maxOf(T a,T b) {
    if (a.compareTo(b) > 0) return a;
    else return b;
}

给泛型添加约束

其中Comparable就是约束

fun<T:Comparable<T>> maxOf(a: T, b: T): T {

可以看一下Java的多个约束

public static <T extends Comparable<T> & Supplier<R>,
    R extends Number>
R callMax(T a, T b) {
    if (a.compareTo(b) > 0) return a.get();
    else return b.get();
}

多个泛型参数

fun <T, R> callMax(a: T,b: T): R
	where T : Comparable<T>,T:()-> R,
		R: Number{
	return if(a> b) a() else b()
}

更常见的是map的key,value

public interface Map<K, out V>

泛型的型变

  • 协变:继承关系一致
  • 逆变:继承关系相反
  • 不变:没有继承关系
不变

此时val list = List.Cons(1.0, List.Nil)会报错,Nil不能满足List<T>

sealed class List<T> {
    object Nil : List <Nothing>()
}
val list = List.Cons(1.0, List.Nil)
协变

out 子类不能替代父类

概念:

Int 继承于 Number

List<Int> 继承于 List<Number>

意义:子类提供子类,父类提供给父类,提供的保持一致

sealed class List<out T> {
    object Nil : List <Nothing>()
}
val list = List.Cons(1.0, List.Nil)
  • 子类Derived兼容父类Base
  • 生产者Producer<Derived> 兼容Producer <Base>
  • 存在协变点的类的泛型参数必须声明为协变或不变
  • 当泛型类作为泛型参数类实例的生产者时用协变
逆变

in 子类可以替代父类

(干垃圾能扔垃圾桶和干垃圾桶, 垃圾只能扔垃圾桶)

public interface Function2<in P1, in P2, out R> : Function<R> {
	public operator fun invoke(p1: P1, p2: P2): R
}
  • 子类Derived兼容父类Base
  • 消费者Consumer <Base>兼容Consumer<Derived>
  • 存在逆变点的类的泛型参数必须声明为逆变或不变
  • 当泛型类作为泛型参数类实例的消费者时用逆变

星投影

'*'可用在变量类型声明的位置 '*'可用以描述一个未知的类型 '*'所替换的类型在:

  • 协变点返回泛型参数上限类型
  • 逆变点接收泛型参数下限类型

泛型实现对比

Java、kotlin使用类型擦除

QQ截图20220924192536
QQ截图20220924192536

内联特化

反射

image-20220925112803220
image-20220925112803220

Gradle引入

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.21")
}

Maven引入

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
</dependency>

与Java对比反射

image-20220925113035776
image-20220925113035776

Java反射

  • 优点:无需引入额外依赖,首次使用速度相对较快
  • 缺点:无法访问Kotlin语法特性,需对Kotlin生成的字节码足够了解

Kotlin反射

  • 优点:支持访问Kotlin几乎所有特性, API设计更友好
  • 缺点:引入Kotlin反射库(2.5MB ,编译后400KB) ,首次调用慢

Kotlin中两种找到函数的方法

class UserDTO(val name:String, val age: Int)

interface Api {
    fun getUser(): List<UserDTO>
}

abstract class SuperType<T>

class SubType: SuperType<String>()

fun main() {
    Api::class.declaredFunctions.first {
        it.name == "getUser"
    }.returnType.arguments.forEach {
        println(it)
    }

    Api::getUser.returnType.arguments.forEach {
        println(it)
    }
}
================================================
UserDTO
UserDTO

对应Kotlin调用Java反射

(Api::class.java.getDeclaredMethod("getUser").genericReturnType as ParameterizedType)
.actualTypeArguments.forEach {
    println(it)
}
===================================
class UserDTO

反射参数

通过下面的这个反射可以看到方法class SubType: SuperType<String>()的泛型参数为String

abstract class SuperType<T> {
    val typeParameter by lazy {
        this::class.supertypes.first().arguments.first().type!!
    }

    val typeParameterJava by lazy {
        this.javaClass.genericSuperclass.safeAs<ParameterizedType>()?.actualTypeArguments?.first()
    }
}

class SubType: SuperType<String>()

fun main() {
    val subType = SubType()
    subType.typeParameter.let (::println)
    subType.typeParameterJava.let (::println)
}

fun <T> Any.safeAs(): T? {
    return this as? T
}
================================================
kotlin.String
class java.lang.String

深拷贝

前提:

data class Group(val id: Int, val name: String, val location: String)

data class Person2(val id: Int, val name: String, val group: Group)

深度拷贝函数:

fun <T : Any> T.deepCopy(): T {
    //如果不是数据类,直接返回
    if (!this::class.isData) {
        return this
    }

    //拿到构造函数
    return this::class.primaryConstructor!!.let { primaryConstructor ->
        primaryConstructor.parameters.map { parameter ->
            //转换类型
            //memberProperties 返回非扩展属性中的第一个并将构造函数赋值给其
            //最终value=第一个参数类型的对象
            val value = (this::class as KClass<T>).memberProperties.first {
                it.name == parameter.name
            }.get(this)

            //如果当前类(这里的当前类指的是参数对应的类型,比如说这里如果非基本类型时)是数据类
            if ((parameter.type.classifier as? KClass<*>)?.isData == true) {
                parameter to value?.deepCopy()
            } else {
                parameter to value
            }


            //最终返回一个新的映射map,即返回一个属性值重新组合的map,并调用callBy返回指定的对象
        }.toMap().let(primaryConstructor::callBy)
    }
}

结果:

注意:3等号比较的是引用,2等号是比较值

fun main() {
    val person = Person2(
        0,
        "zhiyu",
        Group(
            0,
            "Kotlin.cn",
            "China"
        )
    )

    val copiedPerson = person.copy()
    val deepCopiedPerson = person.deepCopy()

    println(copiedPerson)
    println(deepCopiedPerson)

    println(person === copiedPerson)
    println(person === deepCopiedPerson)
    println(person.group === copiedPerson.group)
    println(person.group === deepCopiedPerson.group)
}
=================================================
Person2(id=0, name=zhiyu, group=Group(id=0, name=Kotlin.cn, location=China))
Person2(id=0, name=zhiyu, group=Group(id=0, name=Kotlin.cn, location=China))
false
false
true
false

model映射

map转换为任意类型

inline fun <reified To : Any> Map<String, Any?>.mapAs(): To {
    // 主构造器
    return To::class.primaryConstructor!!.let {
        // 泛型参数
        it.parameters.map { parameter ->
            parameter to (this[parameter.name] ?: if (parameter.type.isMarkedNullable) null
            else throw IllegalArgumentException("${parameter.name} is required but missing"))
        }.toMap()
        // 调用
            .let(it::callBy)
    }
}

map转换为其他map

inline fun <reified From : Any, reified To : Any> From.mapAs(): To {
    return From::class.memberProperties.map { it.name to it.get(this) }
        .toMap().mapAs()
}

实践

class UserDTO2(val id: Int, val login: String, val avatarUrl: String, val url: String, val htmlUrl: String)

class UserVO(val id: Int, val login: String, val avatarUrl: String, val url: String)

fun main() {
    val userDTO = UserDTO2(
        0,
        "Zhiyu",
        "https://static.hetaousercontent.com/labs/squares/random?theme=duskfalling&user_id=2886891",
        "https://github.com/",
        "https://github.com/zhiyu1998/Computer-Science-Learn-Notes"
    )

    val userVO: UserVO = userDTO.mapAs()
    println(userVO)

    val userMap = mapOf(
        "id" to 0,
        "login" to "Zhiyu",
        "avatarUrl" to "https://github.com",
        "url" to "https://github.com/zhiyu1998/Computer-Science-Learn-Notes"
    )

    val userVOFromMap:UserVO = userMap.mapAs()
    println(userVOFromMap)
}
=======================================
UserVO@1046d517
UserVO@3b7d3a38

注解

关键字:annotation

限定标注注解位置Target

@Target(AnnotationTarget.CLASS)
annotation class Api

指定作用时机Retention

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Api

Java相比:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
    public Class type();
}

明确标注对象

@file:JvmName("Hello")
package com.zhiyu.kotlin.annoation.basic