kotlin学习笔记

115 阅读1分钟

lateinit var

lateinit主要用于var声明的变量,然而它不能用于基本数据类型,如Int、Long等,我们需要用Integer这种包装类作为替代,如何让用var声明的基本数据类型变量也具有延迟初始化的效果,一种可参考的解决方案是通过Delegates.notNull,这是利用Kotlin中委托的语法来实现的。我们会在后续介绍它的具体用法,当前你可以通过一个例子来认识这种神奇的效果:

fun main() {
    var test by Delegates.notNull<Int>()
    fun doSomething() {
        test = 1
        println("test value is ${test}")// test = 1
    }
    doSomething()
}

泛型空安全

// 泛型定义处              泛型使用处
//   ↓                      ↓
fun <T> saveSomething(data: T) {
    val set = sortedSetOf<T>()
    //    空指针异常              
    //       ↓
    set.add(data)
}

fun main() {
    //               编译通过
    //                  ↓
    saveSomething(null)
}

在上面的代码中,虽然我们定义的泛型参数是“T”,函数的参数是“data: T”,看起来 data 好像是不可为空的,但实际上,我们是可以将 null 作为参数传进去的。这时候,编译器也不会报错。紧接着,由于 TreeSet 内部无法存储 null,所以我们的代码在“set.add(data)”这里,会产生运行时的空指针异常。

出现这样的问题的原因,其实是因为泛型的参数 T 给了我们一种错觉,让我们觉得:T 是非空的,而“T?”才是可空的。实际上,我们的 T 是等价于 <T: Any?> 的,因为 Any? 才是 Kotlin 的根类型。这也就意味着,泛型的 T 是可以接收 null 作为实参的。

那么,saveSomething() 这个方法,正确的写法应该是怎样的呢?答案其实也很简单:

// 增加泛型的边界限制              
//       ↓                      
fun <T: Any> saveSomething(data: T) {
    val set = sortedSetOf<T>()
    set.add(data)
}

fun main() {
    //              编译无法通过
    //                  ↓
    saveSomething(null)
}

以上代码中,我们为泛型 T 增加了上界“Any”,由于 Any 是所有非空类型的“根类型”,这样就能保证我们的 data 一定是非空的。这样一来,当我们尝试往 saveSomething() 这个方法里传入 null 的时候,编译器就会报错,让这个问题在编译期就能暴露出来。

看到这里,相信你也可以总结出 Kotlin 空安全的第四条准则了:明确泛型可空性

。我们不能被泛型 T 的外表所迷惑,当我们定义 的时候,一定要记住,它是可空的。在非空的场景下,我们一定要明确它的可空性,这一点,通过增加泛型的边界就能做到 <T: Any>。


kotlin中 inilne crossline noinline关键字

inline fun runRunable(block :()->Unit){
    val runnable = Runnable{
        block()
    }
    runnable.run();
}

这段代码会报错,block不能写在这里,以下是具体原因
1、inline关键字是代表runRunable这个函数是内联函数,内联函数的作用是消除lambda的开销,当runRunable被调用的时候,编译器会把当前runRunable的方法的字节码粘贴到被调用的地方,当runRunable前面添加了内联支持,那么该函数的所有函数类型的参数,自动会被内联,也就是说block函数也会被内联,当一个函数被内联之后,那么这个函数中(lambda中就可以直接使用returern关键字进行函数返回),一般情况下lmbda中是不不可以直接用用return关键字进行返回的,要用也必须在return关键字后面加上标签(return@runnable)这种方式,但是这个return只能返回lmbda表达式后面的代码不能执行,外层函数不受影响。
2、这里报错的原因是因为,block被内联,他的代码会直接粘贴到被调用的地方,而且block函数中是可以直接使用return关键字进行返回的,但是它又被放在了另外一个lambda函数中执行,。在lambda中,是不能直接使用return关键字的,所以就会报错(就是说 block会被粘贴到val runnable = Runnable{ block() }中, 并且block可以写return,但是他却放在了Runnable中,相互冲突所以就报错了)总结一句话就是 内联函数可以使用return lambda中却不可以使用rteturn,这两者是冲突的
3.如果需要让内联函数,在另外一个内联函数中被调用的话,需要在当前这个内联函数前面加上crossinline关键字,crossinline关键字,代表由于上面的这中写法,是因为内联函数的lambda表表达式中允许直接使用return关键字和高阶函数的匿名内部类不允许直接使用retunrn关键字之间造成了冲突。二crollinline的关键字就好比一个契约,他用于保证在内联函数中的lambda表达式中一定不能是用return关键字,这样冲突就不会存在了,代码也就可以正常与运行了

inline fun runRunable (crollinline block :()->Unit){
    //crollinline表示 调用runRunable方法的时候传递block的lambda中不可以加return,那么
    //block就可以写在Runnable{}lambda中了
    val runnable = Runnable{
        block()
    }
    runnable.run();
}

中缀函数

中缀函数infix是不能定义成顶层函数的,它必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某个类中;其次infix必须且只能接受一个参数,至于参数的类型没有限制

中缀函数必须是某个类型的扩展函数或者成员方法;·该中缀函数只能有一个参数;·虽然Kotlin的函数参数支持默认值,但中缀函数的参数不能有默认值,否则以上形式的B会缺失,从而对中缀表达式的语义造成破坏;·同样,该参数也不能是可变参数,因为我们需要保持参数数量始终为1个。

infix fun <A,B> A.to(that:B):Pair<A,B> = Pair(this,that)

泛型实化

泛型实化 函数必须是内联函数使用inline关键字 修饰过的函数,且reified关键字需要写在泛型的前面

inline fun <reified T> getType{
        println(T::class.java)
}
inline fun <reified T> startActivity(context:Context){
    val intent = Intent(context , T::class.java)
    context.startActivity(intent)
}
startActivity<TestActivity>(context)

//传递参数
inline fun <reified T> startActivity(context:Context,block:Intent.()->Unit){
    val intent = Intent(context , T::class.java)
    intent.block()//1
    block.invoke(intent)//2
    block(intent)//3
    context.startActivity(intent)
}
startActivity<TestActivity>(context){
    putExtra("param","1")
}

泛型的协变和逆变

1、Student是Person的子类,但是List却不是List的子类,否则将会有类型转换的错误:
假如上述成立那么就会有如下代码

val students = listOf(Student(),Student())
val persons:List<Person> = students//会报错 假设可以成立
val pserson:Personns[0]//从persons集合中取数据 然后转换成Person类型 会报错因为persons集合中都是student实例

操作符重载 操作符重载的函数 和中缀函数一样有且只能有一个参数
操作符可以多次重载,当操作符重载再类中 和 扩展函数中都出现时,会先调用类中的函数

data class User(val age: Int, val name: String) {
    //类中操作符重载 比扩展函数优先级高
    operator fun plus(that: User) = User(that.age + age, (that.name + name))
}
//扩展函数操作符
operator fun User.plus(user: User): User {
    return User(user.age + age, (user.name + name) * 3)
}
//报错'operator' modifier is inapplicable on this function: must have a single value parameter
operator fun User.plus(user: User,str:String): User {
    return User(user.age + age, (user.name + name) * 3)
}

kotlin位运算符,kotlin取消了按位运算符的写法,and=& ,or=|,xor=^


(Int)->((Int)->Unit)  == (Int)->Int->Int//括号可以去掉

假如我们有一个ContryTest类,里面有个放方法为test(),对象的实例为contryTest,我们要引用这个对象的方法,可以这么写 contryTest::test
定义一个类的构造方法引用的变量

class Book(val name:String)
fun main(){
    val getBook = ::Book
    //调用构造方法的引用 getBook的类型就是 (name:Book)->Book
    val book = getBook("kotlin核心编程")
    println(book.name)
    //还可以引用某个类的成员变量 他的类型为 (Book)->String
    val name = Book::name
    
    val bookNames = listOf(Book("kotlin核心编程","flutter实战"))
                            .map(Book::name)
                            
    val bookNames = listOf(Book("kotlin核心编程","flutter实战"))
                            .map{book->
                                book.name
                            }
    // 以上两种学法是等价的
}
高阶函数:

Kotlin存在一种特殊的语法,通过两个冒号来实现对于某个类的方法进行引用。以上面的代码为例,假如我们有一个CountryTest类的对象实例countryTest,如果要引用它的isBigEuropeanCountry方法,就可以这么写:countryTest::isBigEuropeanCountry

class CountryTest{
    fun isBigEuropeanCountry():Boolean{
        println("我被调用了")
        return false
    }
}

fun main() {
    val countryTest = CountryTest()
    val fun1 = countryTest::isBigEuropeanCountry
    fun1.invoke()//我被调用了
    fun1()//我被调用了

    //因为是CountryTest类不是CountryTest的对象,所以调用isBigEuropeanCountry需要一个receiver
    val fun2= CountryTest::isBigEuropeanCountry
    fun2.invoke(countryTest)//我被调用了
    fun2(countryTest)//我被调用了
}

此外,我们还可以直接通过这种语法,来定义一个类的构造方法引用变量。class Book(val name: String)fun main(args: Array) { val getBook = ::Book println(getBook("Dive into Kotlin").name)}可以发现,getBook的类型为(name:String)->Book。类似的道理,如果我们要引用某个类中的成员变量,如Book类中的name,就可以这样引用:Book::name以上创建的Book::name的类型为(Book)->String。

class Book(val name: String)

fun main() {
    
    val book = Book("kotlin核心编程")
    
    val getBook1 = ::Book//getBook的类型为(name:String)->Book
    val getBook2: (name: String) -> Book = ::Book

    //调用
    val book11 = getBook1.invoke("kotlin核心编程")//方式1
    val book12 = getBook1("kotlin核心编程")//方式2

    val book21 = getBook2("kotlin核心编程")//方式1
    val book22 = getBook2.invoke("kotlin核心编程")//方式2

    //调用
    val getName1 = Book::name//(Book)->String
    val getName2: (book: Book) -> String = Book::name

    val name11 = getName1.invoke(book)
    val name12 = getName1(book)
    
    val name21 = getName2.invoke(book)
    val name22 = getName2(book)
}
fun main() {
    listOf(1,2,3)
        .forEach {
//            foo(it)
            foo(it).invoke()
            foo(it)()
        }
}

fun foo(n: Int) = {
    println(n)
}

foo函数的返回类型是Function0。这也意味着,如果我们调用了foo(n),那么实质上仅仅是构造了一个Function0对象。这个对象并不等价于我们要调用的过程本身。通过源码可以发现,需要调用Function0的invoke方法才能执行println方法。所以,我们的疑惑也迎刃而解,上述的例子必须如下修改,才能够最终打印出我们想要的结果:fun foo(int: Int) = { print(int)}>>> listOf(1, 2, 3).forEach { foo(it).invoke() } // 增加了invoke调用 123也许你觉得invoke这种语法显得丑陋,不符合Kotlin简洁表达的设计理念。确实如此,所以我们还可以用熟悉的括号调用来替代invoke,如下所示:>>> listOf(1, 2, 3).forEach { foo(it)() }123

Kotlin还支持一种自运行的Lambda语法:

fun main() {
    println({ x: Int -> println(x) }(1))
    //结果是1
}

这么理解contract函数是个高阶函数 接收一个函数,函数名字为builder 参数为ContractBuilder 返回值为Unit的函数 可以写成public inline fun contract(builder:(ContractBuilder) ->; Unit) { } 类似这样,这样就好理解了一些了,把ContractBuilder 对象传递到lambda中这样就可以直接用ContractBuilder对象(隐士参数it)的方法了。原本写法ContractBuilder.() 这样只是一个扩展函数,这样写,在lambda中就可以直接使用this,或者可以去省去this直接调用ContractBuilder对象的方法了。一个语法糖,其实编译之后的字节码是一样的
如下图 String代表ContractBuilder,三种写法都可以,但是第一种写法,调用的时候更烦方便

但是让我不理解的是fun2中,lambda中隐士参数是this 也就是String对象,可以直接调用String的方法 而fun1和fun2中lambda隐士参数是it,只能通过it来调用传递进来的String对象的方法 我看了下 编译过后的代码 其实这三个方法都是会生成一个Function1实例,然后调用invoke方法 把String传递进去的,没有任何区别 请大神帮忙解释一下:其实是语法糖

lambda可以直赋值给个函数比如

   fun foo(num:Int) = {
    println(num)
   }
   fun foo(num:Int):(Int)->Unit{
        println(num)
   }
   listOf(1,2,3,4).forEach{
        foo(it)()//
        foo(it).invoke()
   }

kotlin Unit关键字

如何理解Unit?其实它与int一样,都是一种类型,然而它不代表任何信息,用面向对象的术语来描述就是一个单例,它的实例只有一个,可写为()。


kotlin枚举

enum class DayOfWeek(private val day: Int) {
    MON(1),
    TUE(2),
    WEN(3),
    THU(4),
    FRI(5),
    SAT(6),
    SUN(7);

    fun getDayNumber(): Int {
        return day
    }
}

设计模式原则

1、开闭原则:
对扩展开放,对修改关闭
2、里氏替换原则:
子类可以实现父类的抽象方法,但不能复写父类的非抽象方法;
子类可以增加自己特有的方法;
当子类的方法实现父类的方法时,方法的前置条件(即方法的参数)要比父类方法的输入参数更宽松;
当子类的方法实现父类的抽象方法的时,方法的后置条件(及方法的返回值)要比父类更加严格;


    var list3 :MutableList<*> = mutableListOf(1,"2",true)
    val list4:MutableList<out Any> = mutableListOf(1,"2",true)
    协变
   这两个list是一样的 *代表的就是泛型的上界  out & extends

携带接收者的labmda

    fun html(init :Html.()->Unit):Html{   
        val html = Html()    
        init(html) //第一种   
        init.invoke(html)   //第二种   
        html.init()    //第三种   
        return html
    }
   
    class Htmlfun body(){ 
        }
    }
    //调用
    html {
        body()
    }

扩展方法
当扩展函数定义在class中的时候,只有当前类和子类可以访问,背后会在当前类中声明一个非静态的public方法
当扩展函数定义在kt文件中的时候,任何类都可以访问,需要导包

同名的类成员方法的优先级高于扩展方法,当扩展函数和现有类的成员方法同时存在时,Kotlin将会默认使用类的成员方法。

扩展函数始终是静态调度的

companion object伴生对象可以添加扩展函数

class Son{
    companion object{
        val gae = 10
    }
}

fun Son.Companion.foo(){
    print(age)
}

Son.foo()

扩展函数可以被继承示例如下:

open class Person(val name: String)

class User(name: String) : Person(name)

//person 扩展函数
fun Person.printName() {
    println("Person name is $name")
}

//fun User.printName(){
//    println("User name is $name")
//}

fun main() {
    val person = Person("tom")

    person.printName()//Person name is tom

    val user = User("jack")

    user.printName()//Person name is jack

    //如果User.printName接触注释,那么user.printName()将会打印User name is jack
}
* * *
    **扩展属性**
    扩展属性不能有初始化器,以下代码不能通过编译 不能添加=0
val String.asd:Int = 0    
get() {      
        return  length    
    }

匿名内部类:

    fun start(runnable: Runnable) {
        runnable.run()
    }
    
   start {
        println("start")
   }
   
  start2 {
        println("start2")
   }
    
  start之所以可以这么调用其实是背后kotlin做了转换,生成了start2方法
  因为函数是内联函数,所以block会被复制调用地方可以使用return返回整个函数,由于他是在Runnable的 lambda中调用, lambda中不允许直接returen 所以需要加crossinline
  
  创建了一个runnable匿名类,然后把传递进来的lambda包装了一下
  inline fun start2(crossinline block: () -> Unit) {    
        Runnable {                      
                 block.invoke()   
        }
  }

        object : Runnable{
            override fun run() {
                
            }
        }
        
        =
        相当于编辑器生成了一个函数名为Runnable返回数据为Runnable接口的高阶函数
        val r = Runnable {   
            
        }

        =
     
inline fun Runnable(crossinline block:()->Unit):Runable{    
    return object : Runnable{       
        override fun run() {       
            block()     
        }    
    }
}