第二章 Kotlin入门

第二章 Kotlin入门

  1. 工作原理

    编程语言大致分为两类:编译型语言和解释型语言。Java属于解释性语言,Java代码编译生成class文件,这种class文件只有Java虚拟机能识别。Java虚拟机担当解释器角色,在程序运行时将变异后的class文件解释称计算机可识别的二进制数据后再执行。

    Kotlin的编译器能将Kotlin代码编译成class文件。Java虚拟机不会关心class文件是从Java编译来的,还是从Kotlin编译来的,只要是符合规格的class文件,它都能识别。这就是Kotlin的工作原理。

  2. 变量和函数

    1. 变量

      只允许变量前声明两种关键字:val和var

      val(value简写)声明一个不可变的变量,永远优先使用val来声明变量

      var(variable简写)声明一个可变的变量

      val a: Int = 10
      

      Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。

    2. 函数

      函数(function)和方法(method)没有本质区别,不同语言的叫法习惯不同而已。

      fun(function简写)定义函数的关键字。fun后跟着是函数名。接着是一对括号,括号内是参数。括号后是返回值,无返回值时直接不写或者写为Unit

      fun functionName(arg1: Int, arg2: Int): Int {
          return 1
      }
      

      当函数只有一行代码时,Kotlin允许不写函数体,直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。

      fun largeNumber(arg1: Int, arg2: Int) = max(arg1, arg2)
      
  3. 程序的逻辑控制

    程序的执行语句分为顺序语句、条件语句和循环语句。

    1. 条件语句

      1. if条件语句

        if条件语句与Java无异

        Kotlin中if语句比Java中多了一个额外功能,if语句可以有返回值

        fun largeNumber(num1: Int, num2: Int): Int {
            val largeInt = if (num1 > num2) {
                num1
            } else {
                num2
            }
            return largeInt
        }
        
      2. when条件语句

        when类似Java中的switch

        switch缺陷:只能传入char, byte, short, int, Character, Byte, Short, Integer, String, or an enum变量作为条件。并且每个case需要break,否则执行完当前case后依次执行下面case。

        基本示例:

        fun getScore(name: String) = when (name) {
            "Tom" -> 85
            "Jim" -> 77
            else -> 0
        }
        

        类型匹配示例:

        fun checkNumber(num: Number) = when (num) {
            is Int -> "Int"
            is Double -> "Double"
            else -> "not support type"
        }
        

        is是类型匹配的关键字,相当于java中的instanceof关键字。

        when不带参数的示例,Kotlin中判断字串或对象使用==关键字,不用像Java使用equals()方法

        fun getScoreNoParam(name: String) = when {
            name.startsWith("lisa") -> 99
            name == "Tom" -> 85
            name == "Jim" -> 77
            else -> 0
        }
        
    2. 循环语句

      1. for循环语句

        Java中for-i循环已被舍弃,Java中for-each循环被加强变成了for-in循环

        Kotlin中区间的写法:

        val range = 0..10 // [0,10]    
        val range2 = 0 until 10 // [0,10)
        
        for (i in 0..10) {
            print("$i  ")
            // 输出 0  1  2  3  4  5  6  7  8  9  10  
        }
        

        使用step关键字跳过奇数元素,相当于Java中的i = i + 2操作

        for (i in 0 until 10 step 2) {
            print("$i  ")
            // 输出 0  2  4  6  8  
        }
        

        downTo定义降序区间

        for (i in 10 downTo 0) {
            print("$i  ")
            // 输出 10  9  8  7  6  5  4  3  2  1  0  
        }
        
      2. while循环不管是在语法还是使用技巧上都和Java中的while循环没有任何区别

  4. 面向对象编程

    1. 类

      File通常是用于编写Kotlin顶层函数和扩展函数的

      Class是用于编写Kotlin的类

      类示例:

      class Person {
          var name = "Tony"
          var age = 10
      
          fun introduceSelf() {
              println("My name is $name. I am $age years old.")
          }
      }
      

      类示例化:

      val person = Person()
      person.introduceSelf()
      
    2. 继承和构造函数

      1. 定义Student类继承自Person:

        1. 使Person可继承,给Person加上open关键字。在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。

          open class Person {
          	......
          }
          
        2. 使Student继承Person

          class Student : Person() {
            init {
            	...      
            }
          	...
          }
          

          Person后面为何加括号:子类构造函数必须调用父类构造函数。

          Kotlin中构造函数有:主构造函数和次构造函数。主构造函数没有函数体,Kotlin提供了init结构体。子类的主构造函数调用父类的哪个构造函数,在继承的时候通过括号来指定。

      2. 改造Person

        Person改造如下:

        open class Person(val name:String, val age:Int) {
            fun introduceSelf() {
                println("My name is $name. I am $age years old.")
            }
        }
        

        Student作出相应调整:

        class Student(val number: Int, val grade : Int, name:String, age:Int) : Person(name, age) {
            init {
        
            }
        }
        

        Student构造函数中的name和age不能声明成var或者val,因为声明为val或者val会成为该类的字段。

      3. 次构造函数

        类只能有一个主构造函数,但是可以有多个次构造函数。所有次构造函数都必须调用主构造函数。

        示例如下:

        class Student(val number: Int, val grade: Int, name: String, age: Int) : Person(name, age) {
            constructor(name: String, age: Int) : this(0, 0, name, age) {
            }
        
            constructor() : this("Tom", 0) {
            }
        }
        

        次构造函数是用constructor定义的。实例化代码如下:

        val student1 = Student()
        val student2 = Student("Jack", 10)
        val student3 = Student(1527, 2, "Jerry", 21)
        

        类只有次构造函数,没有主构造函数:

        class Student : Person {
        
            constructor(number: Int, grade: Int, name: String, age: Int) : super(name, age) {
        
            }
        
            constructor(name: String, age: Int) : this(0, 0, name, age) {
        
            }
        
            constructor() : this("Tom", 0) {
        
            }
        }
        

        在次构造函数中参数不能用var或val声明,因为var或者val声明的参数会成为类的字段。次构造函数不是必须调用的,所以存在未调用导致未初始化,所以禁止使用var或者val声明。

    3. 接口

      Kotlin中接口和Java中接口几乎一致。

      1. 接口使用:

        1. 创建Study接口

          interface Study {
              fun readBooks()
              fun doHomework()
          }
          
        2. 实现接口

          class Student(name: String, age: Int) : Person(name, age), Study {
              override fun readBooks() {
              }
          
              override fun doHomework() {
              }
          }
          

          熟悉Java的人一定知道,Java中继承使用的关键字是extends,实现接口使用的关键字是implements,而Kotlin中统一使用冒号,中间用逗号进行分隔。

        3. 示例:

          fun main() {
              val student = Student("Jack", 10)
              doStudy(student)
          }
          
          fun doStudy(study: Study) {
              study.doHomework()
              study.readBooks()
          }
          

          因为Student实现了Study接口,所以Student类可以传递给doStudy函数,接着调用Study接口的方法。这种叫做面向接口编程,也就是多态。

      2. 默认实现

        interface Study {
            fun readBooks()
            fun doHomework() {
                println("doHomework default implementation")
            }
        }
        

        默认实现的函数可自由选择实现或不实现。不实现会使用默认实现逻辑。

      3. 可见性修饰符

        修饰符 Java Kotlin
        public 所有类可见 所有类可见(默认)
        private 当前类可见 当前类可见
        protected 当前类、子类、同一包下类可见 当前类、子类可见
        default 同一包下类可见(默认) 无
        internal 无 同一模块中类可见
    4. 数据类和单例类

      1. 数据类

        数据类通常需要重写equals()、hashCode()、toString()方法

        equals() 判断数据是否相等

        hashCode() HashMap、HashSet等hash相关类正常工作

        toString() 清晰的日志输出

        示例:

        data class Cellphone(val brand: String, val price: Double)
        

        在类前面声明data表示这个类是数据类。Kotlin会自动实现equals()、hashCode()、toString()等固定无实际逻辑意义的方法。

      2. 单例类

        全局只有一个实例

        object Singleton {
            fun singletonTest() {
                println("SingletonTest is called")
            }
        }
        

        使用

        Singleton.singletonTest()
        
  5. Lambda编程

    1. 集合创建和遍历

      1. 创建List

        listOf<String>("Hello", "world", "this", "is", "kotlin")
        
      2. 遍历List

        for (s in listOf) {
            println(s)
        }
        
      3. listOf()函数创建的是一个不可变的集合,mutableListOf()函数为可变集合

        val mutableList = mutableListOf<String>("Hello", "world", "this", "is", "kotlin")
        
      4. 创建Map

        var fruitAndPrice :HashMap<String, Int> = HashMap()
        fruitAndPrice["Apple"] = 3
        
        val fruitAndIndex = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "pear" to 4, "Grape" to 5)
        
      5. 遍历Map

        for ((fruit, number) in fruitAndIndex) {
            println("fruit is $fruit, index is $number")
        }
        
    2. 集合的函数式API

      1. 取长度最长的单词

        val wordList = listOf<String>("Hello", "world", "this", "is", "kotlin")
        // 当集合中无元素时,结果会为空
        val maxByOrNull = wordList.maxByOrNull { it.length }
        // 当结果为空时,使用空字串
        val result: String = maxByOrNull ?: ""
        println(result)
        
      2. Lambda是一段可以作为参数传递的代码,Lambda语法结构

        {参数名1 : 参数类型, 参数名2 : 参数类型 -> 函数体}
        
      3. 简化推导过程

        上述maxByOrNull只是一个简单的函数,此函数的内部实现会遍历集合,并且调用lambda表达式(集合元素传入表达式)获取元素的长度并对比,最终返回最长的集合元素。

        1. lambda表达式作为变量

          val wordList = listOf<String>("Hello", "world", "this", "is", "kotlin")
          val function = { fruit: String -> fruit.length }
          val maxByOrNull = wordList.maxByOrNull(function)
          
        2. 直接作为参数传递

          val maxByOrNull = wordList.maxByOrNull({ fruit: String -> fruit.length })
          
        3. 当lambda是函数的最后一个参数时,可以将lambda表达式移到括号外

          val maxByOrNull = wordList.maxByOrNull() { fruit: String -> fruit.length }
          
        4. 如果lambda是函数的唯一参数时,可以将括号省略

          val maxByOrNull = wordList.maxByOrNull { fruit: String -> fruit.length }
          
        5. Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型

          val maxByOrNull = wordList.maxByOrNull { fruit -> fruit.length }
          
        6. 当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替

          val maxByOrNull = wordList.maxByOrNull { it.length }
          
      4. Java函数式API使用

        限制:在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。

        以Runnable举例:

        1. Java实现:

          new Thread(new Runnable() {
              @Override
              public void run() {
                  TODO("Not yet implemented")
              }
          });
          
        2. Kotlin实现:

          Thread(object : Runnable {
              override fun run() {
                  TODO("Not yet implemented")
              }
          })
          

          Kotlin舍弃了new关键字,改用object关键字

          再次精简,因为Runnable只有一个待实现方法,无需显式地重写run()方法

          Thread(Runnable {
              TODO("Not yet implemented")
          })
          

          如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略

          Thread({
              TODO("Not yet implemented")
          })
          

          如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略

          Thread {
              TODO("Not yet implemented")
          }
          
  6. 空指针检查

    1. 可空类型

      fun doStudy(study: Study) {
          study.doHomework()
          study.readBooks()
      }
      

      上述代码没有空指针风险,因为Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数也一定不会为空。

      Kotlin将空指针异常的检查提前到了编译时期,如果我们的程序存在空指针异常的风险,那么在编译的时候会直接报错。

      允许为空示例:

      fun doStudy(study: Study?) {
          if (study != null) {
              study.doHomework()
              study.readBooks()
          }
      }
      
    2. 判空辅助工具

      1. 借助?.操作符将if判断语句去掉

        fun doStudy(study: Study?) {
            study?.doHomework()
            study?.readBooks()
        }
        
      2. ?:操作符,这个操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果

        一般写法:

        val a:String = "a"
        val c = if (a == null) a else ""
        

        ?:操作符写法:

        val c = a ?: ""
        

        示例获取字串的长度

        fun getWordLength(word : String) : Int {
            if (word == null) {
                return 0
            } else {
                return word.length
            }
        }
        

        简化后:

        fun getWordLength(word : String?) : Int {
            return word?.length ?: 0
        }
        
      3. *非空断言工具,不推荐,这是有风险的写法

        fun getWordLength(word : String?) : Int {
            return word!!.length
        }
        
      4. 辅助工具——let

        fun doStudy(study: Study?) {
            study?.let { stu ->
                stu.readBooks()
                stu.doHomework()
            }
        }
        

        let函数可以处理全局判空问题,而if判断语句无法做到这点。

        我们将doStudy()函数中的参数变成一个全局变量,使用let函数仍然可以正常工作,但使用if判断语句则会提示错误。

        之所以这里会报错,是因为全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if语句中的study变量没有空指针风险。

        var student: Student? = Student("Lucy", 19)
        
        fun study() {
            if (student != null) {
                student.doHomework()
                student.readBooks()
            }
        }
        
  7. Kotlin中小魔术

    1. 字符串内嵌表达式

      class Student(name: String, age: Int) : Person(name, age), Study {
          override fun readBooks() {
              println("My name is $name, I'm ${age} years old. I'm reading!")
          }
      }
      
    2. 函数默认参数

      fun sing(songName:String = "Pretty Girl", artist:String) {
          
      }
      

      调用:

      sing("hello", "jams")
      // 通过键值对赋值
      sing(artist = "lucy")
      

      主构造函数中参数添加默认值,避免多个构造函数

      class Student(val number: Int = 0, val grade: Int = 0, name: String="Tom", age: Int=10) : Person(name, age), Study {
      
      }
      
posted @ 2021-06-28 01:03  youngly15  阅读(165)  评论(0)    收藏  举报