Kotlin

https://www.bilibili.com/video/BV1Ph411C7dG?p=5&spm_id_from=pageDriver
视频在P30之前还是值得看一看的,但是后面讲的太乱没有条理,感觉是想到什么讲点什么,一味的讲怎么用,具体能解决什么问题没有讲,示例也是Kotlin官网的示例,感觉没必要看了。

学习笔记:

P1 Kotlin中的新类型:函数类型

参考视频
kotlin中新增了一种Java不存在的类型–函数类型
函数类型的对象:可以当函数来使用,可以作为函数的参数、函数的返回值、赋值给变量
创建函数类型对象有三种方式:双冒号加函数名、匿名函数、lambda表达式

//函数a的类型:(Int) -> Int
fun a(sum: Int): Int{ return sum}
//创建函数类型对象
//第一种:双冒号加函数名,::a是指向对象的引用,而这个对象具有a函数的功能,但不是a函数本身
val b = ::a
//第二种:匿名函数
val c = fun (sum: Int): Int{ return sum}
//第三种:lambda表达式,lambda表达式其实就是为了简化函数类型的写法,不用像上边的两个例子,只需要一句话就能写完
val d = { sum:Int-> sum }
//那么a这个函数的函数类型是什么呢?(Int) -> Int
//定义一个函数e,参数有两个,第一个是Int类型,第二个是一个函数类型(Int) -> Int,而 a 函数恰好符合这个函数类型
fun e (g: Int, f: (Int) -> Int){ println(f(g)) }
e(1,::a)
//将函数类型对象赋值给变量
val h = b

p4 可空?、if

  • int和int?是两种不同的类型
  • 在需要int?类型参与运算前需要判断是否为空
  • if…else语句不需要return也可有返回值,返回的是语句块的最后一句的值,但是如果只有if的话不能直接写=号来返回,必须要有else。
fun main() {
    val a = 1
    val b = 2
    val max = if (a > b){
        a
    }else{
        b
    }
}
  • 在if中只有一句执行语句可省略大括号{}
fun main() {
    val a = 1
    val b = 2
    val max = if (a > b) a else b
}
?. ?: as?
	//假设一个值为可空类型
    val s :String? = null
    //错误代码:s为可空类型,不能直接使用,需要做判空处理
    s.length
    //s为空则返回空
    println(s?.length)//输出:null
    //s的length为null则返回0
    println(s?.length?:0)//输出:0
   //类型转换as
    val i = s as Int
    println(i)//编译不报错,运行时错误
    //类型转换as?
    val i = s as? Int
    println(i)//输出为null

p5 关键字 Any is 、遍历数组、when

知识点:

  1. Any是顶层类,Any和int/String一样也是一个数据类型,想当于java中的Object,但是和Object不是一会事
  2. is关键字,相当于java中的instanceof,判断对象的类型
fun text (s: Any) : Any{
    return if (s is String) s.toUpperCase() else s
}
fun main() {
    val s = text(123)
    print(s)
}
  • 相同功能用java实现比较繁琐,主要是需要Object强制转换到某一个类型
public class Test {
    private Object test(Object s) {
        if (s instanceof String) {
            String s1 = ((String) s).toUpperCase();
            return s1;
        }else {
            return s;
        }
    }
    public static void main(String[] args) {
        Test test = new Test();
        String s2;
        Object abc = test.test("abc");
        s2 = (String) abc;
    }
}

知识点:

  1. intArrayof就是java中的int[]数组,在kotlin中没有基本数字类型,int也是包装类,但是在JVM中则编译为int类型。
  2. in关键字遍历
  3. array.winhIndex()中即包含了下标也包含了值
  4. array.indeices的值是一段0…4
fun main() {
    println("-------第一种遍历方法---------")
    val array = intArrayOf(1,2,3,4,5)
    for (item in array)
        println(item)
    println("-------第二种遍历方法---------")
    for (i in array.indices)
        println("array[$i] = ${array[i]}")
    println("-------第三种遍历方法---------")
    for ((index,value) in array.withIndex())
        println("array[$index] = $value")
}

知识点:

  1. when自动返回最后一行代码的值
  2. 可以有区间 in 2..10
  3. when中必须包含参数所有可能的赋值范围所对应的
fun strLength(string: String)=when(string.length){
    1 -> "字符串长度是1"
    in 2..10 ->  "字符串长度是2-10之间"
    else -> "字符串长度大于10"
}

P6 函数式编程、lambda表达式、常用的集合操作符 fifter map forEach any all find

函数式编程

fun main() {
    val array = listOf("hello","world","hallo world","welcome")
    array.filter { it.length > 5 }.map { it.toUpperCase() }.sorted().forEach { println(it) }
}
  • lambda表达式省略步骤:
    maxBy有迭代器(看源码)
    val array = listOf("hallo","world","hallo world","welcome")
	val lambda = { str:String -> str.length}
	//lambda作为参数传入函数中
	array.maxBy(lambda)
	//lambda表达式代入函数中
    array.maxBy({ str:String -> str.length }) 
    //当lambda表达式时函数的最后一个参数时,可以将lambda表达式移到括号的外面
    array.maxBy(){ str:String -> str.length }
    //当lambda是唯一参数时,括号也可以省略
    array.maxBy{ str:String -> str.length }
    //maxBy函数有迭代器,所以传入lambda的参数是array的元素,所以lambda表达式的参数类型可以推导,所以省略
    array.maxBy{ str -> str.length }
    //lambda参数列表中只有一个参数时,参数也可以省略,可以用it代替
    //如果省略参数,表达式中的参数必须用it
    array.maxBy{ it.length }
  • 常用的函数 fifter map forEach any all find

这些函数共同的特点:

  1. 都是可迭代的(源码)
  2. 参数都是lambda代码
  3. 源码中可知这些函数都是Iterable迭代器的扩展函数,而listarray等都是可迭代的
val array = listOf("hallo","world","hallo world","welcome")
//功能:筛选长度大于5的字符串,将这些字符串全部大写,并按字母排序,并打印
    array.filter { s: String -> s.length > 5 }.map { s: String -> s.toUpperCase() }.sorted()
    .forEach { s: String->println(s) }
    //lambda代码简化后可写为
    array.filter { it.length > 5 }.map { it.toUpperCase() }.sorted()
    .forEach { println(it) }
	//是否存在一个元素的长度大于5,返回值为Boolean
    array.any{ it.length > 5 }
	//是否所有元素的长度大于5,返回值为Boolean
	array.all { it.length > 5 }
	//查找满足条件的,返回第一个符合条件的元素
	array.find { it.length > 5 }
    
    //判断array中是否有元素
val array1 = listOf<String>()
    println(array1.any()) //false

P7构造函数、构造函数中参数和属性的区别

类的构造函数:

  1. 次构造函数必须直接或者间接的调用主构造函数
  2. 次构造函数在执行时,先调用this中的构造函数,再调用本身
class Person1 constructor(var name: String){
    private var age: Int
    private var address: String
    init {
        println(name)
        age = 0
        address = ""
    }
    constructor(name: String ,age:Int):this(name){
        println("name=$name,age=$age")
        this.age = age
    }
    constructor(name: String,age: Int,address:String):this(name,age){
        println("name=$name,age=$age,address=$address")
        this.address = address
    }
}

fun main() {
    val person1 = Person1("zhangsan")
    val person2 = Person1("lisi",20)
    val person3 = Person1("wangwu",30,"hangzhou")
}

在类中,如果参数前没有val或者var,类认为该变量是一个参数而不是类的属性,在类中无法访问,只能作为参数给父类传值

class Person1 constructor(name: String, var age: Int,var address: String){
    fun printInfo(){
        println("name=$name, age=$age, address=$address")
    }
}
//报错Unresolved reference: name 

name和age变量作为参数,传值给父类

open class Person1 (val name: String,val age: Int)

class Student1 (name: String, age: Int,var address: String):Person1(name, age)

P8继承

继承:

  1. 类可以被继承需要open关键字修饰类
  2. 方法可以被子类改写,需要在父类的方法前加open关键字,子类方法前加override关键字
  3. 如果子类已经改写了父类的方法,但是不想被孙子类改写,加final关键字
//类可以被继承需要open关键字修饰类
open class Person2 (val name:String,val age: Int){
//方法可以被子类改写,需要在父类的方法前加open关键字
    open fun printInfo(){
        print("name")
    }
}

open class Student2 (name: String,age:Int,var address:String):Person2(name,age){
//子类改写父类的方法,子类方法前加override关键字
//加final关键字,孙子类无法改写此方法
    final override fun printInfo(){
        println("name=$name,age=$age,address=$address")
    }
}

class Pupil (name: String,age: Int,address: String,val grade: String): Student2(name, age, address){
    
}

属性的继承:

  1. 需要在父类的属性加open关键字,才可在子类中重写
  2. JVM中val关键字修饰该属性只有get方法,var修饰的关键字有get/set方法,所以继承属性时,var可以继承valval不能继承var
open class Person2 (open val name:String,val age: Int)

open class Student2 (name: String,age:Int,var address:String):Person2(name,age){
    override var name: String = "zhangsan"
}

fun main() {
    val student = Student2("wangwu",30,"hangzhou")
    println("name=${student.name}")
}

继承时方法重名:

  1. 子类继承的两个父类中,都有同一个方法,则编译器要求子类必须重写这个重复的方法
  2. 调用父类中重名的方法,使用 super<父类名>.方法名
interface Parent{
    fun method(){
        println("parent")
    }
}
open class Parent1{
    open fun method(){
        println("parent1")
    }
}

class SonClass:Parent,Parent1(){
    override fun method() {
        super<Parent1>.method()
        super<Parent>.method()
    }
}

P9对象 object、伴生对象 companion object

object定义的对象

  1. kotlin中,class关键字声明的是一个类,object关键字声明的是一个对象
  2. object对象中不允许有构造函数
  3. 常量用const val定义,变量用var定义
  4. 通过类名+属性名/类名+方法名访问

object Myobject{
    const val name = "tom"
    var age = 20
    fun method(){
        println("object对象")
    }
}

fun main() {
    Myobject.age = 30
    println(Myobject.name)
    println(Myobject.age)
    Myobject.method()
}

伴生对象 companion object:

由来:在kotlin中没有static关键字,无法通过static关键字在类中定义静态属性和方法,所以产生了伴生对象companion object

  1. 伴生对象的调用和java中的static一样,类名.属性名/类名.方法名来调用

为什么叫伴生对象?看起来伴生对象和java中的static一样,但是在jvm中伴生对象是类的真实对象的实例成员,可以通过反编译求证。
在JVM中,示例的Mytest类中,伴生对象有一个实例对象(如果实例对象companion object没有定义名字,则默认的名字是Companion),所以调用时,实际上调用的是MyTest类中,实例对象Companion的属性和方法。

class MyTest{
    companion object{
        const val name = "tom"
        var age = 20
        fun method(){
            println("伴生对象的method方法")
        }
    }
}
fun main() {
	println(MyTest.name)
    MyTest.age = 30
    println(MyTest.age)
    MyTest.Companion.method()
}

P10属性赋值、获取是怎么实现的 ?、扩展函数、延迟初始化

属性赋值、获取是怎么实现的 ?

  1. Person3(name: String, address: String)括号中的叫参数,不是属性。属性是类的成员,参数是用来给属性或者方法传递值的
  2. val只有get属性,var有get和set属性,所以在继承时var可以继承val,val不能继承var
  3. field关键字,只能用在这个地方,field是真是储存属性值的地方
class Person3(name: String, address: String) {
    val name: String = name
        get() {
            println(" name 的get方法")
            return field
        }
    var address: String = address
        get() {
            println("address的get方法")
            return field
        }
        set(value) {
            println("address的set方法")
            field = value
        }
}

延迟初始化:

  1. lateinit只能用在类中声明的属性中,不能用在构造函数中
  2. 属性不能自定义setget
  3. 属性不能为空(不能加?)比如:lateinit var age: String?,属性的类型不能是原生类型,比如:lateinit var age: Int
class TheClass{
    lateinit var age: String
}

扩展函数:
看上去好像是将扩展函数增加到了类中,JVM中类里面并没有此方法

class ExtensionTest{
    fun add(a: Int,b: Int) = a + b

    fun sub(a: Int,b: Int) = a - b
}
fun ExtensionTest.multiply(a: Int,b: Int): Int{
    return a * b
}
fun main() {
    val extensionTest = ExtensionTest()
    extensionTest.multiply(2,2)
}
  1. 扩展函数是静态解析的
  2. 扩展函数不支持多态,调用只取决于对象的声明类型
    下面示例中,虽然BB继承AA,但是调用扩展函数时,只取决于fun myPrint(a: AA)中的参数的声明类型,声明类型为AA则调用AA的扩展函数
open class AA
class BB : AA()
fun AA.aa()= println("AA的扩展函数")
fun BB.aa()= println("BB的扩展函数")
fun myPrint(a: AA){
    a.aa()
}
fun main() {
    myPrint(AA())
    myPrint(BB())
}

P12 扩展函数的作用域

  1. A类的扩展函数写在B类中,A类的实例称为扩展接收者,B类的实例称为分发接收者
  2. 扩展函数调用的方法在两个类中都有定义,扩展接收者的优先级高于分发接收者
  3. 虽然代码写在B类中,但是B类中的方法和B类的实例都无法直接调用A类的扩展函数
class AClass{//实例称为扩展接收者
    fun method1() {
        println("AClass-method1")
    }
}
class BClass{//实例称为分发接收者
    fun method1() {
        println("BClass-method1")
    }
    fun method3() {
        println("BClass-method3")
    }
    fun AClass.method4(){
        method1()//A、B类中都有method1方法,扩展接收者优先级高
        this@BClass.method1()//调用分发接收者的method1方法,this@BClass
        method3()//即可以直接调用A类中的方法,也可以直接调用B类中的方法
        
    }
    fun method5(){
        val aClass = AClass()
        aClass.method4()//method4虽然定义在B类中,但是在类中也不能直接调用,需要通过A类的实例来调用
    }
}

fun main() {
    val bClass = BClass()//B类的实例,也无法直接调用A类的扩展函数
    bClass.method5()
}

P13 数据类 data class、解构

  • 数据类要满足以下3个条件
  1. 主构造方法中至少有一个参数
  2. 主构造方法中的参数必须有val或var属性
  3. 数据类不能是abstractopensealedinner
  • 数据类的特点:
  1. 数据类会自动生成equals()hashCode()toString()方法
  2. 在JVM中,会自动生成主构造方法中属性对应的componentN方法,N是属性在主构造方法中的位置,例如:示例中name属性的位置是1,则生成 component1()方法
  3. component方法的作用:获得属性的值,此方法是用来解构声明的。见示例
  4. JVM中有如下的方法:fun component1() = name
data class Person4(val name: String, var age: Int, var address: String)
fun main() {
    val person = Person4("tom", 20, "beijing")
    //component方法的作用
    var(name, age, address) = person
    println("$name, $age, $address")
}
//结果:tom, 20, beijing

如果给每个属性都赋初值,那么这个类就有了无参构造方法

data class Person5(val name: String = "", var age: Int = 0, var address: String = "")
fun main() {
    val person1 = Person5()
    println("$person1")
}
//结果:Person5(name=, age=0, address=)
  • 解构:
  1. 根据数据类在JVM中自动创建componentN方法的特性实现解构
  2. 方法前要加operator 关键字
  3. operator还用于属性委托中,见P23
  4. operator解释:将一个函数标记为重载一个操作符或者实现一个约定
class Person7(val name: String,val age: Int){
    operator fun component1() = name
    operator fun component2() = age
}

fun main() {
    val (name ,age) = Person7("tom", 20)
    println("$name,$age")
}

P14 密封类 sealed class 泛型 generice

密封类 sealed class

  1. 表现出一种受限的层次结构,像父类和子类的结构
  2. 密封类及其子类必须在同一个文件中 密封类是抽象类构造方法是私有的
  3. 个人感觉密封类和when配合才有意义,因为when() 的条件中会强制要求把所有的可能都包括进去,所以密封类无论有多少子类,在when下都必须处理
sealed class Calculator
class Add: Calculator()
class Sub: Calculator()
class Mul: Calculator()
fun calculator(a: Int, b: Int, cal: Calculator)=when(cal){
    is Add -> a + b
    is Sub -> a - b
    is Mul -> a * b
}
fun main() {
    println(calculator(1, 2, Add()))
    println(calculator(1, 2, Sub()))
    println(calculator(1, 2, Mul()))
}

泛型 generic,表示变量类型的参数化(使用时才确定参数的类型)

变量类型的参数化理解:
在示例中,TM就是变量类型的形参,实例化类时,传入的Int, String就是实参,这就是将变量的类型参数化,类型都是通过参数传参的方式来传递,不是原本就定义好的

//T、M就是变量类型的形参
class Generic<T, M>(a: T,b: M)
fun main() {
	//传入的`Int, String`就是实参
    val generic = Generic<Int, String>(1,"abc")
}

通常使用的泛型所表示的含义

<T> type 类型
<E> element 元素
<K> key 键
<V> value 值 

泛型类,泛型接口,泛型方法

//泛型类
class Generic<T>{
    fun gen(a: T){//T是类传递过来的
        println("这是一个泛型类,但不是一个泛型方法")
    }
//**泛型方法**:泛型的形参是在方法名前面自己定义的,而不是由类传递过来的
    fun <E> gen1(b: E){//E是方法自己定义的
        println("这是一个泛型方法")
    }
}
//泛型接口
interface Generic1<T>{
    fun gen(t: T)
}

P15 -P18 协变和逆变:

参考第一行代码 P419
为什么会有协变和逆变,他们解决了什么问题?
原因:

在下面的示例中,SonParent的子类,在MyData方法中参数只能传MyClass<Parent>()实例,那如何才能传入MyClass<Son>()作为参数呢?

open class Parent
class Son : Parent()

class MyClass<T>

fun MyData(data: MyClass<Parent>){}

fun main() {
    MyData(MyClass<Parent>())
    MyData(MyClass<Son>())//报错:Kotlin: Type mismatch: inferred type is MyClass<Son> but MyClass<Parent> was expected
}

在这里插入图片描述

协变:

假设AB的子类,现在MyData方法参数只接收MyClass(A),如何能将MyClass(B)也作为参数传入呢?那么我们就需要MyClass<T>这个类是协变

//协变,只读(输出get())
class MyClass<out T>(val data: T?){
    fun method(): T?{//T只能用在返回值类型
        return data
    }
}
//简单的说data参数只能是MyClass<Parent>类或者其子类,例如:MyClass<Son>
fun MyData(data: MyClass<Parent>){
    TODO("")
}

fun main() {
    MyData(MyClass<Parent>())
    MyData(MyClass<Son>())
}

逆变:

假设AB的子类,现在MyData方法参数只接收MyClass(B),如何能将MyClass(A)也作为参数传入呢?,那么我们就需要MyClass<T>这个类是逆变

//逆变
class MyClass<in T>{
	fun method(t : T){//T只能用在参数里面
		.....
	}
}
//简单的说data参数只能是MyClass<Son>类或者其父类,例如:MyClass<Parent>
fun MyData(data: MyClass<Son>){}

fun main() {
    MyData(MyClass<Parent>())
    MyData(MyClass<Son>())
}

P19 嵌套类(默认)、内部类(inner)

嵌套类:

  • 示例中InClass是嵌套类,相当于JAVA中的static修饰的静态内部类。
  • 访问时通过外部类的类名.方式直接访问。
  • 嵌套类无法直接访问外部类的属性和方法,需要通过外部类的实例来访问。
class OutClass{
    private var str = "out String"
    fun outMethod(){
        println("out method")
        //外部类可访问嵌套类的方法和属性
        InClass().method()
    }
    class InClass{
        fun method(){
            println("in method")
        }
    }
}
fun main(){
    OutClass.InClass().method()
}

内部类:

  • 内部类有inner关键字修饰
  • 访问内部类的方法和属性,需通过外部类的实例来访问。
  • 内部类访问外部类的属性和方法,通过this@外部类类名.的方式
class OuterClass{
    private var str = "outer class string"
    fun outMetod(){
        println("out method")
        InnerClass().inMethod()
    }
    inner class InnerClass{
        fun inMethod(){
            println("in method")
            println(this@OuterClass.str)
        }
    }
}
fun main(){
    OuterClass().InnerClass().inMethod()
}

P20 对象表达式,可自定义方法,可继承类,可实现接口

  • 对象表达式:
  1. 代替JAVA的匿名类
  2. 可同时实现一个或多个接口和抽象类的方法
val myObject = object  {
	//自定义方法
}
val myObject = object :抽象类名,接口名 {
	//实现所有的抽象方法
}
myObject.方法名
myObject.属性名
//对象表达式
interface MyInterface{
    fun myPrint(number:Int):Int
}
abstract class MyAbstractClass(val age: Int){
    abstract var key : Int
    abstract fun myMethod(age: Int)
    fun myMethod1(){
        println("抽象类中的非抽象方法执行,构造函数中获得的属性值 $age")
    }
}

fun main() {
//实现单个抽象方法
    val myObject1 = object: MyInterface{
        override fun myPrint(number: Int): Int {
            println("对象表达式实现接口")
            return number
        }
    }
    println(myObject1.myPrint(0))
    println("--------------------")
    //实现单个抽象类
    val myObject2 = object : MyAbstractClass(20){
        override var key: Int = 0
            get() = field
            set(value) {
                println("抽象类中抽象属性赋值操作")
                field = value}

        override fun myMethod(age: Int) {
            println("对象表达式实现的抽象类")
        }
    }
    myObject2.age //访问主构造函数的属性
    println("抽象类中,抽象属性初始化的值 $myObject2.key")//属性的get方法
    myObject2.key = 1 //属性的set方法
    println("抽象类中,抽象属性set后的值 $myObject2.key")
    myObject2.myMethod(0) //实现的抽象方法
    myObject2.myMethod1() //抽象类中的非抽象方法
    println("------------------------")
    //同时实现抽象方法和抽象类
    val myObject3 = object : MyAbstractClass(20),MyInterface{
        override fun myPrint(number: Int): Int {
            TODO("Not yet implemented")
        }

        override var key: Int
            get() = TODO("Not yet implemented")
            set(value) {}

        override fun myMethod(age: Int) {
            TODO("Not yet implemented")
        }

    }
}
  • 对象表达式实现JAVA的接口:
    只有一个方法的接口称为函数式接口,才可以使用lambda表达式
//java的接口:
public interface P20 {
    void myPrint();
}
//kotlin中对象表达式实现JAVA的接口
//示例中应用了lambda表达式的简化,仅限于实现JAVA的单接口,并且接口中只有一个方法
    val myObject4 = P20 { println("对象表达式实现JAVA的接口") }
    myObject4.myPrint()

P21对象表达式、对象声明和对象表达式的区别

  • 对象表达式没有继承类或者接口,myObject2的类型是Any
  • 当没有继承的对象表达式作为类的成员时,必须有private修饰,否则无法正确识别其类型
//对象表达式作为类的成员时,必须有private修饰,否则无法正确识别其类型

class MyClass{
    private val myObject2 = object {
        fun myPrint(){
            println("没有实现抽象类和接口,自己定义的方法")
        }
    }
    fun myTest(){
        myObject2.myPrint()
    }
}

fun main() {
    MyClass().myTest()
}

  • 对象表达式可以是变量也可以是方法(对象表达式无敌了)
interface MyInterface1{
    fun myPrint(number:Int):String
}
class MyClass{
    fun myMethod() = object : MyInterface1{
        override fun myPrint(number: Int): String {
            return "对象表达式实现的匿名对象"
        }
    }
}

fun main() {
    println(MyClass().myMethod().myPrint(1))
}

对象表达式和对象声明之间的区别:
对象表达式是立刻初始化的。对象声明是延迟初始化,是在首次访问的时候初始化的

P22枚举、委托类、类委托

不懂枚举是干嘛用的

enum class Seaon{
    SPRING,SUMMER,AUTUMN,WINTER
}

enum class Seaon1{
    SPRING{
        override fun getSeaon(): Seaon1 = SPRING
    },
    SUMMER{
        override fun getSeaon(): Seaon1 = SUMMER
    },
    AUTUMN{
        override fun getSeaon(): Seaon1 = AUTUMN
    },
    WINTER{
        override fun getSeaon(): Seaon1 = WINTER
    },
    ;
    abstract fun getSeaon():Seaon1
}

fun main() {

}
  • 类委托:

重点在委托类是怎么写的

  1. 与受委托类实现相同的接口
  2. 参数类型也是该接口
  3. by后面是该接口类型的参数
interface MyInterface2{
    fun myPrint()
}

class MyImpl : MyInterface2 {
    override fun myPrint() {
        println(this.javaClass)
    }
}
/*	1. 与受委托类实现相同的接口
*	2. 参数类型也是该接口
	3. `by`后面是该接口类型的参数*/
class MyClass1 (myInterface2: MyInterface2) : MyInterface2 by myInterface2

fun main() {
    val myImpl = MyImpl()
    MyClass1(myImpl).myPrint()
}

P23属性委托、延迟属性lazy、非空属性

  • 属性委托:
  1. getValuesetValue函数前加关键字operator
  2. 函数加了两个参数:第一个参数是属性所有者的类型或所有者的父类型,第二个参数表示属性本身,这个属性类型为KProperty
  3. 可以用过property.name访问属性名称
  4. val定义的属性在委托中只有getValue方法
  5. getValue返回的可以是属性的类型及其子类型
  6. val属性默认实现ReadOnlyProperty接口,var属性默认实现ReadWriteProperty接口
    自己的理解:var str by MyDelegate(),是把属性委托给了个一个类的对象,所以这个对象就是属于这个属性的
class MyClass2{
//属性委托给了个一个类的对象
    var str  by MyDelegate()
}

class MyDelegate {
    operator fun getValue(thisRef: MyClass2,property: KProperty<*>) = "$thisRef,属性本身:${property.name}"
    operator fun setValue(thisRef: Any?,property: KProperty<*>,value: String) = println("$thisRef,属性本身:${property.name}")
}

fun main() {
    var myClass = MyClass2()
    myClass.str = "hello world"
    println(myClass.str)
}
  • 延迟属性:
  1. 第一次调用属性时会执行lazy的lambda表达式,并给属性赋值
  2. 以后再调用属性,则只会返回属性的值,不再执行lambda表达式
  3. 必须是val定义的属性(如果定义为var暂时没有找到解决办法)
val myLazy : Int by lazy {
    println("这条语句只会执行一次")
    100
}

fun main() {
    println(myLazy)
    println(myLazy)
}
  • 非空属性:(感觉和lateinit var一样)
  1. 非空属性在后期必须要赋值,所以类型必须是var定义
//    非空属性
    var address:String by Delegates.notNull<String>()
    lateinit var address : String

P24可观测属性、map属性委托

  • 可观测属性
  1. Delegates.observable作用:发现属性值发生改变后可执行
  2. Delegates.vetoable作用:在属性值放生改变之前,如果lambda表达式返回true则赋新值;如果返回false则保留原来的值
  3. 这连个方法由两个参数,第一个参数是属性的初值,第二个参数是lambda表达式
  4. 必须赋初值
class Person4(age: Int){
    var manAge: Int by Delegates.observable(age){
        property, oldValue, newValue ->
        println("属性:${property.name}由:$oldValue 更改为:$newValue,")
    }
    var womenAge: Int by Delegates.vetoable(age){
        property, oldValue, newValue -> when {
            oldValue <= newValue -> {
                println("属性:${property.name}由:$oldValue 更改为:$newValue,")
                true
            }
            else -> {
                println("属性:${property.name},年龄不能减少")
                false
            }
         }
    }
}
fun main() {
    val person4 = Person4(30)
    person4.manAge = 20
    println("new manAge = ${person4.manAge}")
    println("------------------------------------")
    person4.womenAge = 40
    println("1.new womenAge = ${person4.womenAge}")
    person4.womenAge = 30
    println("2.new womenAge = ${person4.womenAge}")
}
  • map属性委托:
  1. Map的只读属性和读写属性定义:Map<String,Any> MutableMap<String,Any>
  2. 在示例的Student2的类中,age定义为valmap类型为MutableMap,不能用student2.age = 30修改属性值,但是可以用map2["age"] = 30的方法来修改mapvalue值,也就间接的修改了age的属性值,所以在Kotlin中一定特别注意只读和可读写两种属性的应用。
  3. 在源码中可以看到Map类型只有get属性,MutableMapsetget属性。
//map属性委托
class Student1(map: Map<String,Any>){
    val name: String by map
    val age: Int by map
    val birthday: Date by map
}

class Student2(map: MutableMap<String,Any>){
    var name: String by map
    val age: Int by map
    var birthday: Date by map
}


fun main() {
    val map1 = mapOf(
            "name" to "tom",
            "age" to 20,
            "birthday" to Date()
    )
    val student1 = Student1(map1)
    println("name is ${student1.name},age is ${student1.age},birthday is ${student1.birthday}")
    println("--------------------------")
    val map2 = mutableMapOf(
            "name" to "tom",
            "age" to 20,
            "birthday" to Date()
    )
    val student2 = Student2(map2)
    println("name is ${student2.name},age is ${student2.age},birthday is ${student2.birthday}")
    student2.name = "lucy"
    //student2.age = 30
    map2["age"] = 30
    println("name is ${student2.name},age is ${student2.age},birthday is ${student2.birthday}")
}

P26 函数及其参数,当lambda表达式作为参数时

函数:存在于类体之外
方法:存在于类体内

参数式函数类型:

  1. lambda可以作为实参传入函数
  2. 如果参数是一个方法,实参用::方法名
  3. 如果lambda表达式是最后一个参数,可以将lambda表达式放在括号外面
  4. compute:(x: Int, y: Int) -> Int)传入的实参要和参数compute中定义的参数类型、个数、返回值类型一样
fun sub(a: Int, b: Int): Int{
    return a - b
}
fun sum(a: Int, b: Int): Int{
    return a + b
}

fun test(a: Int, b: Int,compute:(x: Int, y: Int) -> Int): Int{
    return compute(a, b)
}
fun main() {
    //参数是一个方法,传参用::方法名
    var n1 = test(1, 2, ::sub)
    var n2 = test(1, 2, ::sum)
    //直接传入lambda表达式
    val n3 = test(1,2,{x,y-> x * y})
    //lambda表达式是最后一个参数时,可以将lambda表达式放在()外面
    val n4 = test(1,2){x,y-> x / y}
}

P27参数为可变参数vararg

  • 参数为可变参数:
  1. 形参加vararg修饰
  2. 实参可以为("a","b","c")形式
  3. 实参可以为数组,数组前需加分散运算符*
fun test(vararg string: String){
    string.forEach{println(it)}
}
fun main() {
    test("a","b","c")
    test(*arrayOf("d","e","f"))
    val array = arrayOf<String>("g","h","i")
    test(*array)
}
  • 可变参数传参示例
fun <T>covertList(vararg element: T): ArrayList<T>{
    val result = ArrayList<T>()
    element.forEach { result.add(it) }
    return result
}

fun main() {
    println(covertList("a","b","c",1))
    val arrayele = arrayOf("d","e","f",2)
    println(covertList("zhangsan","lisi",*arrayele))
}
  • 单表达式函数可以省略返回类型
  1. 函数体只有一条执行语句时可以直接写在等号后面,并且不必声明返回值类型
fun test(a: Int,b: Int) = a + b
  • 显式返回类型不可以省略返回类型
  1. 函数体不止一条执行语句时,执行语句必须写在{}里面,并且必须声明返回值类型

P28中缀符号infix、内联函数inline

  • 中缀符号(infix notation):
  1. 类的成员方法,或者扩展函数使用
  2. 方法前加关键字infix
  3. 只有一个参数
class InfixTest(val a: Int){
    infix fun add(b: Int) = a + b
}
fun main() {
    val infixTest = InfixTest(2)
    //以下两种方式是等价的
    println(infixTest.add(5))
    println(infixTest add 5)
}
  • 内联函数(inline function)
  1. 在函数前加inline关键字
  2. 相当于把被调用函数的代码直接写到调用函数的地方,不存在调用的关系 ,具体体现在字节码中。
inline fun myCalculate(a: Int,b: Int) = a + b
fun main() {
    myCalculate(1, 2)
}
  • 高阶函数(high-order function)与 lambda
  1. lambda表达式:{参数1:参数类型,参数2:参数类型 -> 执行体}
  2. 参数的类型可以在lambda表达式中声明,也可以在变量定义的时候声明
  3. 变量的类型为函数类型
  4. lambda表达式中有多条执行语句时,用;间隔,返回最后一条语句的值
    val constant = {a:Int, b:Int -> a+b}
    val constant1:(Int,Int) -> Int = {a, b-> a + b }
    val constant2:(Int,Int) -> Int = {a, b-> a + b;println(b);a }

P29函数式参数的应用,自定义fifter

  • 定义String的扩展函数fifiter,对字符串中的每个字符进行操作
fun String.fifter(predicate: (Char) -> Boolean): String{
    val str:StringBuilder = StringBuilder()
    val toCharArray = this.toCharArray()
    toCharArray.forEach { if (predicate(it)) str.append(it) }
    return str.toString()
}
fun main() {
	
    val str = "abc1".fifter {
    //字符串中每个字符是否式字母
        it.isLetter()
    }
    print(str)
}

P30 lambda表达式就是用来代替匿名函数

  • lambda表达式就是用来代替匿名函数
  1. lambda表达式非常的简洁
fun main() {
//    虽然可以在main中定义函数,由于函数没有名字,无法调用
    fun (x: String) = x.length > 5
    val str = arrayOf("hello","world")
    //匿名函数的应用
    str.filter ( fun (x: String) = x.length > 4 ).forEach ( fun (x: String){ println(x) })
//    lambda表达式代替匿名函数
    str.filter { it.length > 4 }.forEach { println( it ) }
}

P31带接收者的函数字面值-匿名函数

  • 带接收者的函数字面值:(感觉是另一个版本的扩展函数)
  1. 自己的理解:和扩展函数类似的感觉,扩展函数是给某一个类增加了一个方法。带接收者的函数字面值是给一个类型增加了一个方法
  2. 调用的时候也和扩展函数相似
fun main() {
	//带接收者的函数字面值,用lambda表达式实现
    val test1 : Int.(other: Int) -> Int = { other -> this - other }
    //用匿名函数实现
    val test2 = fun Int.(other:Int) : Int = this + other
    //匿名函数多条执行语句
    val test3 = fun Int.(other:Int) : Int { val temp = this - other; return temp  * 2}
    //调用
    println(2.test2(3))//5
    val test4 : String.() -> Int = { this.length}
    println("abd".test4())
}

P33 list的一部分函数、Range、异常

  • list的一部分函数
fun main() {
    val str = listOf("a","b","c")
    val first = str.first()//列表第一个元素
    val last = str.last()//列表最后一个元素
    val filter = str.filter { it > "a" }//过滤器,返回符合过滤器条件的列表
    val requireNoNulls = str.requireNoNulls()//列表中有没有null元素,如果有则返回一个异常,没有则返回字符串本身
    val none = str.none()//列表是否为空,返回Boolean
    val none1 = str.none { it > "a" } //列表中符合条件的元素集合是否为空,返回Boolean
    val firstOrNull = str.firstOrNull()//列表中第一个元素是否为空,返回值String?
}
  • Range:
    for (a in 1..4){}//a=1;a<=4;a++
    for (a in 4..1){}//不会执行的原因:a=4;a<=1,判断表达式不成立,所以不会执行
  • 异常:
  1. try() catch()会返回最后一行代码的值
//kotlin中的try()catch()会返回最后一行代码的值
fun main() {
    val i = "a"
    val s = try {
        parseInt(i)
    }catch (e : NumberFormatException){
        null
    }
    println(s)
}

P34 throw 表达式、注解

  • throw 表达式
  1. throw表达式的类型:Nothing
  2. Elvis表达式:?:,如果?:前的值不为空直接赋值,为空则返回表达式后面的值
fun method(string: String): Nothing{
    throw IllegalAccessError(string)
}
fun main{
    val s = null
    val s1 = s ?: throw IllegalAccessError("值不能为空")
    val s2 = s ?: method("值不能为空")
}
  • 注解
  1. 定义注解的关键字annotation class
  2. @Target 注解的目标,例如:类,方法,属性,表达式,get方法
  3. @Retention 是否在运行时、二进制或源代码中保留
  4. @MustBeDocumented 生成文档
//Target注解中决定自定义的注解可以在什么位置
@Target(AnnotationTarget.CLASS,AnnotationTarget.FUNCTION,AnnotationTarget.VALUE_PARAMETER,
        AnnotationTarget.EXPRESSION,AnnotationTarget.PROPERTY_GETTER)
//策略
@Retention(AnnotationRetention.SOURCE)
//必须出现在文档
@MustBeDocumented
//定义注解的关键字annotation class
annotation class MyAnnotation

@MyAnnotation
class MyClass{
    var b = 10
     @MyAnnotation get() = field
    @MyAnnotation
    fun method(@MyAnnotation a: Int):Int{
         var a= @MyAnnotation 10
        return 10
    }
}

P35 Kotlin中引用JAVA代码

  • Kotlin中引用JAVA
  1. 在Kotlin中,将来自于JAVA的声明类型称为平台类型
  2. Kotlin不会在编译器检查
  3. 在运行期,只要是来自平台类型值的传递,首先生成一个断言,判断是否为null,将及时抛出异常,不会将空值的错误蔓延
fun main() {
    val person = Person()
    person.name = "tom"
    person.age = 20
    person.marryied = false
    // Kotlin引用JAVA时,基本没有什么区别
    person.name = null
    val name : String = person.name
    //在明确的知道person.name为空时也不报错
    /*由于Kotlin和JAVA对null值的要求程度不同,所以在Kotlin中,将来自于JAVA的声明类型称为平台类型,
        kotlin不会在编译器检查,只要是来自平台类型值的传递,在运行期首先生成一个断言,判断是否为null,
        将及时抛出异常,不会将空值的错误蔓延*/
}

P36 Kotlin调用JAVA

  1. 由于Kotlin和JAVA对null值的严格程度不同,所以在Kotlin中,将来自于JAVA的声明类型称为平台类型
  2. 来自平台类型的值在Kotlin中传递时,Kotlin编译器不会检查,但是在运行时会生成一个断言,判断是否为null,及时抛出异常,不会将空值的错误蔓延
fun main() {
    val person = Person()
    person.name = "tom"
    person.age = 20
    person.marryied = false
    // Kotlin引用JAVA时,基本没有什么区别
    person.name = null
    val name : String = person.name
    //在明确的知道person.name为空时也不报错
    /*由于Kotlin和JAVA对null值的要求程度不同,所以在Kotlin中,将来自于JAVA的声明类型称为平台类型,
        kotlin不会在编译器检查,只要时来自平台类型值的传递,在运行期首先生成一个断言,判断是否为null,将及时抛出异常,不会将空值的错误蔓延*/
}

P38 JAVA调用Kotlin(1)

  • JAVA调用Kotlin中的方法和属性:在Kotlin字节码中,创建了一个Kotlin文件名的类,方法和属性都是该类的静态方法,在JAVA中可直接通过 文件名.方法名文件名.属性名直接访问
  • 既然在字节码中一个Kotlin文件名的类,JAVA中可以实例化(new)吗?不可以,应为在字节码中没有该类的构造方法。

P39 JAVA调用Kotlin中伴生对象的方法和属性(2)

  • 将两个Kotlin文件在字节码中合并成一个文件

需要在两个Kotlin文件中加入以下代码:
@file: JvmName("合并后的名字")
@file: JvmMultifileClass

  • JAVA中调用Kotlin中伴生对象的属性:
  1. java调用伴生对象的属性:类名.Companion.setName("lucy"); 类名.Companion.getName();
  2. 伴生对象的属性加上注解@JvmField,属性称为类的public属性,调用时:类名.属性名
    Kotlin代码:
class People{
    companion object{
        var name = "tom"
        @JvmField
        var age = 20
    }
}

JAVA代码:

    public static void main(String[] args) {
        People.Companion.setName("lucy");
        People.age = 10;
        System.out.println(People.age);
    }
  • JAVA中调用Kotlin中伴生对象的方法:
  1. java调用伴生对象的方法:类名.Companion.test1();
  2. 伴生对象的方法加上注解@JvmStatic,在JVM中此方法前会加static关键字,调用时:类名.方法名
class People{
    companion object{
        fun test1(){}
        @JvmStatic
        fun test2(){}
    }
}

P40类型擦除、@JvmName()

  • 类型擦除:
  1. 示例中定义的两个myFilter()扩展方法,虽然扩展的类型不同,但是在JVM中会忽略类型(类型擦除),所以在JVM中认为这两个方法是重复的。
  2. 需要通过注解@JvmName("方法名")来修改方法名,修改方法名目的只为了在JVM中两个方法名字不同。
  3. Kotlin中调用这两个方法,还是用原来的方法名,Kotlin会通过类型自动判断应该调用哪一个方法。
fun List<String>.myFilter(): List<String>{
    return listOf("hello","world")
}
@JvmName("myFilter2")
fun List<Int>.myFilter(): List<Int>{
    return listOf(1,2)
}

fun main() {
    val list = listOf(1,2)
    list.myFilter()
}
  • @JvmName(“方法名”):
  1. 示例中定义的属性a,在JVM中会生成getA()方法,和方法重名。
  2. 需要通过注解@JvmName("方法名")来修改方法名,修改方法名目的只为了在JVM中两个方法名字不同。
  3. Kotlin中调用时,还和原来一样。
class MyTest{
    val a: Int
        get() = 20
    @JvmName("getVelue")
    fun getA() = 30
}

P44反射:函数引用

  • 函数引用:
  1. 简单来说函数引用就是将一个函数作为参数传递。
  2. 当函数做为参数传入时,函数引用::,见示例第一部分
  3. 函数也可以赋值给以一个变量,传参时传入变量名,见示例中第二部分
  4. 类的方法赋值给变量时,变量的类型需要特别注意,不能使用自动推导的变量,见示例中的第三部分
//函数引用

fun multiply(x: Int): Int{
    return x * 3
}
fun multiply(string: String): Int{
    return string.length
}
fun test(list: Array<String>,m: (String) -> Int){
    for (a in list){
        println(m(a))
    }
}
fun main() {
//示例第一部分
    val array = arrayOf(1,2,3,4)
    val array1 = arrayOf("hello","world","hello world")
    array.map (::multiply).forEach { println( it ) }
    test (array1,::multiply)
    println("-------------------------")
    //示例第二部分
    val myReference: (Int) -> Int = ::multiply
    val myReference1: (String) -> Int = ::multiply
    array.map(myReference).forEach { println( it ) }
    test(array1,myReference1)
    println("------------------")
    //示例第三部分
    val myReference2: String.(Int)->Char = String :: get
    val string = "welcome"
    println(string.myReference2(0))
    println("------------------")
    //示例第四部分
    val myReference3: Test3.()->Int = Test3::multiply
    println(Test3(string).myReference3())
}

class Test3(val string: String){
    fun multiply(): Int{
        return string.length
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值