Android开发基础——Kotlin简介

Posted 止步听风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发基础——Kotlin简介相关的知识,希望对你有一定的参考价值。

什么是Kotlin

Java代码在运行前需要编译生成一种特殊的class文件,然后Java虚拟机会识别并解释这些class文件,而Kotlin作为一种新的编程语言,就是将其代码同样编译生成为这样的class文件,也就是说,虽然看起来是两者是两种不同的编程语言,但其实其实质都是一样的。

如何运行Kotlin代码

运行Kotlin代码大致存在三种方法:

  • 使用IDEA工具,创建Kotlin项目,然后就可以运行Kotlin代码
  • 在线运行Kotlin代码
  • 使用android Studio,虽然Android Studio创建的是Android项目,但是只要编写Kotlin的main函数,仍然可以独立运行Kotlin代码

这里便使用Android Studio运行Kotlin代码。

如在MainActivity的同级包结构下创建一个Kotlin文件,然后编写HelloWorld:

package com.example.helloworld

fun main() 
    println("Hello world!")

在Android Studio的run中输出结果为:

Hello world!

不过这里编译中途报了一个错:

Manifest merger failed with multiple errors, see logs

这是因为sdk和Kotlin编译器版本不匹配,解决版本:

  • 在file-setting-system setting-android sdk中下载低版本的sdk,比如Android 11(api 30)
  • 然后在build.gradle中,将compileSdk和targetSdk的参数修改为30

变量和函数

变量

在Kotlin中定义一个变量,只允许在变量前声明两种关键字:

  • var:value,用于声明一个不可变的变量,这种变量在初始赋值之后就不能重新赋值,对应Java中的final变量
  • var:variable,用于声明一个可变的变量,这种变量在初始赋值之后仍可以重新赋值,对应Java中的非final变量

比如,这样的代码是合理的:

package com.example.helloworld

fun main() 
    val a = 10
    var b = 20
    b = 30
    println("a =" + a + ",b = " + b)

这是因为Kotlin采用了类型推导机制,但如果要对一个变量延迟赋值的话,就需要显式声明变量类型:

package com.example.helloworld

fun main() 
    val a = 10
    var b: Int
    b = 30
    println("a =" + a + ",b = " + b)

此时也就限定了变量b只能为Int类型,而不能使用其它类型的值进行赋值。

同时从上面可以看到,Kotlin的变量类型Int是大写的,这与Java不同,这也就意味着Kotlin中变量采用的对象数据类型,即Int是一个类,其它数据类型也是类似的,也需要大写。

函数

Kotlin中函数的语法规则为:

fun func(param1:Int, param2:Int):Int 
    return 0

其形式为:

  • fun:为定义函数的关键字
  • func:为函数名
  • param1:Int:为参数声明格式,前边为参数名,后边为参数类型
  • Int:最后的Int为返回值类型,这部分是可选的

比如,下面的代码就简单说明了函数的使用:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int 
    return max(num1, num2)


fun main() 
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))

同时上面的形式还可以使用Kotlin中的语法糖形式。当一个函数中只有一行代码时,Kotlin中可以不用编写函数体,而将唯一的一行代码写在函数定义的末尾。比如上面的代码就可以转化为:

package com.example.helloworld

import kotlin.math.max

//fun Larger(num1:Int, num2:Int):Int = max(num1, num2)
fun Larger(num1:Int, num2:Int) = max(num1, num2)

fun main() 
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))

逻辑控制

if条件语句

Kotlin中的条件语句主要有两种实现形式:if语句和when语句。

先看一下if条件语句:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int 
    if (num1 > num2) 
        return num1
     else 
        return num2
    


fun main() 
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))

而Kotlin中的if语句还有一个额外的功能,就是if语句是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。因此,上面的代码可以写为:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int 
    return if (num1 > num2) 
        num1
     else 
        num2
    


fun main() 
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))

即可以使用if语句来为return提供返回值,或者是为变量赋值。

而采用之前提到的语法糖的形式,又可以将代码变为:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int = if (num1 > num2) num1 else num2

fun main() 
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))

when条件语句

when条件语句有点像是Java中的switch语句。

package com.example.helloworld

import kotlin.math.max

fun getScore(name:String) = if (name == "Tom") 
    86
 else if (name == "Jim") 
    77
 else if(name == "Jack") 
    95
 else 
    0


fun main() 
    println("Score =" + getScore("Tom"))

上面的代码使用when条件语句就是:

package com.example.helloworld

import kotlin.math.max

fun getScore(name:String) = when (name) 
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 95
    else -> 0


fun main() 
    println("Score =" + getScore("Tom"))

而当执行逻辑只有一行代码时,可以省略。

同时when语句还允许进行类型匹配:

package com.example.helloworld

import kotlin.math.max

fun checkNumber(num:Number) 
    when (num)
        is Int -> println("num is Int")
        is Double -> println("num is Double")
        else -> println("no support")
    


fun main() 
    checkNumber(10.1)

同时还可以使用when的不带参数的用法:

package com.example.helloworld

import kotlin.math.max

fun getScore(name:String) = when  
    name == "Tom" -> 86
    name == "Jim" -> 77
    name == "Jack" -> 95
    else -> 0


fun main() 
    println("Score =" + getScore("Tom"))

循环语句

Kotlin中循环语句也存在两种形式,while循环和for循环。

不过Kotlin中的while循环和Java中的while循环使用方法类似,这里只说明for循环。

package com.example.helloworld

fun main() 
    for (i in 1..10) 
        println(i)
    

在上面的for循环中,1..10是区间的形式,即[1, 10]。可以看出这里的for循环和Java中的for循环形式还是有区别的。

但如果需要区间[1, 10)的话,就是下边的形式:

package com.example.helloworld

fun main() 
    for (i in 1 until 10) 
        println(i)
    

而如果要修改步长的话,就是下边的形式:

package com.example.helloworld

fun main() 
    for (i in 1 until 10 step 2) 
        println(i)
    

而如果需要一个降序的闭区间,就是下边的形式:

package com.example.helloworld

fun main() 
    for (i in 10 downTo 1) 
        println(i)
    

面向对象编程

类与对象

package com.example.helloworld

class Person 
    var name = ""
    var age = 0

    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


fun main() 
    var tmp = Person()
    tmp.name = "Tom"
    tmp.age = 15

    tmp.printInfo()

从上面来看,Kotlin中类的定义和实例化和Java是类似的,只是去掉了new关键字。

继承和构造函数

这里先看一个继承的简单示例:

package com.example.helloworld

open class Person 
    var name = ""
    var age = 0

    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student:Person() 
    var grade = 0



fun main() 
    var tmp = Student()
    tmp.name = "Tom"
    tmp.age = 15
    tmp.grade = 3

在上面的继承中,存在两个和Java代码不同的地方:

  • open关键字:用该关键字声明该类是可继承的,这是因为Kotlin中默认类是final的,即不可继承的,因此需要首先声明该类是可以被继承的
  • 继承形式:Java中使用extends关键字来表示继承关系,而Kotlin中使用冒号:来表示继承,同时还需要在父类后添加括号

上面只是Kotlin中继承的简单示例,这里再看一下构造函数。Kotlin中构造函数分为两种:主构造函数和次构造函数。

主构造函数没有函数体,直接定义再类名后面,每个类默认都会有一个不带参数的主构造函数,用户也可以为其显式指明参数。

package com.example.helloworld

open class Person 
    var name = ""
    var age = 0

    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student(val grade:Int):Person() 



fun main() 
    var tmp = Student(10)
    tmp.name = "Tom"
    tmp.age = 15

上面直接在构建Student对象时就进行了属性赋值,而如果需要在主构造函数中编写一些代码,就需要init结构体:

class Student(val grade:Int):Person() 
    init 
        println("grade is " + grade)
    

这里就可以解释为什么Kotlin类继承中父类后需要存在括号。根据继承特性的规定,子类的构造函数必须调用父类的构造函数,可是主构造函数并没有函数体,因此Kotlin中的子类主构造函数通过括号指定调用父类的哪个构造函数。

也即上面的内容中,表示Student类的主构造函数在初始化时会调用person类的无参构造函数,即使在午餐的情况下,该括号也不可省略。

package com.example.helloworld

open class Person(val name:String, val age:Int) 
    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student(name:String, age:Int, val grade:Int):Person(name, age) 
    init 
        println("grade is " + grade)
    


fun main() 
    var tmp = Student("Tom", 10,3)

上面的形式就可以调用父类的有参构造形式,不过需要保证父类存在该形式。

而在Student类的参数声明中,也没有将父类的参数声明为val,这是因为在主构造函数中声明为val或var的参数将自动称为该类的字段,会导致冲突。因此Student中name和age的声明前不用加任何关键字,使其作用域限定在主构造函数中。

而次构造函数在一个类中可以存在多个,次构造函数也可以用于实例化一个类,这点和主构造函数没有什么不同,只是次构造函数是有函数体的。

Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(间接调用):

package com.example.helloworld

open class Person(val name:String, val age:Int) 
    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student(name:String, age:Int, val grade:Int):Person(name, age) 
    init 
        println("grade is " + grade)
    

    constructor(name:String, age:Int):this(name, age, 0) 
    

    constructor():this("",0) 
    


fun main() 
    var tmp = Student("Tom", 10,3)

从上面可以看到,次构造函数是通过sonstructor关键字来定义的,上面定义了两个次构造函数,又通过次构造函数调用了主构造函数,完成了类对象的实例化。

而当类中只有次构造函数,没有主构造函数时:

package com.example.helloworld

open class Person(val name:String, val age:Int) 
    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student:Person 
    constructor(name:String, age:Int):super(name, age) 
    

    constructor():super("",0) 
    


fun main() 
    var tmp = Student()

比如上面的代码,Student没有显式定义主构造函数,但是存在两个次构造函数,即此时没有主构造函数,而既然子类没有主构造函数,继承父类时就不需要加上括号了。而由于没有主构造函数,次构造函数只能直接调用父类的构造函数,因此也就使用了super关键字。

接口

package com.example.helloworld

interface Study 
    fun readBook()
    fun doHomework()


open class Person(val name:String, val age:Int) 
    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student(name:String, age:Int, val grade:Int):Person(name, age), Study 
    init 
        println("grade is " + grade)
    
    override fun readBook() 
        println(name + " is reading.")
    

    override fun doHomework() 
        println(name + " is doing Homework.")
    


fun doStudy(study: Study) 
    study.readBook()
    study.doHomework()


fun main() 
    var tmp = Student("Tom", 10,3)
    doStudy(tmp)

上面的代码中,定义了父类和接口,并在子类中继承了父类,实现了接口。Java中继承使用extends关键,接口实现使用implements关键字,而Kotlin中统一使用逗号分隔。同时接口后面不需要添加括号,因为其没有构造函数调用。

子类接口中方法的实现要使用override关键字来声明,而在main中调用doStudy,doStudy接口Study接口的形式,然后调用接口中函数,这个过程就叫做多态。

同时Kotlin中还支持对接口的默认实现:

package com.example.helloworld

interface Study 
    fun readBook()
    fun doHomework() 
        println("do homework default implements")
    


open class Person(val name:String, val age:Int) 
    fun printInfo() 
        println("name is " + name + ",age is " + age)
    


class Student(name:String, age:Int, val grade:Int):Person(name, age), Study 
    init 
        println("grade is " + grade)
    
    override fun readBook() 
        println(name + " is reading.")
    


fun doStudy(study: Study) 
    study.readBook()
    study.doHomework()


fun main() 
    var tmp = Student("Tom", 10,3)
    doStudy(tmp)

上面接口的定义中,doHomework中存在函数体,因为接口实现只会强制要求实现readBook,而不会要求强制实现doHomework,这样子类Student调用的就是接口的默认实现了。

在上面的代码中,并不对属性和方法进行隔离,即没有使用可见性修饰符对其变量和方法进行修饰,而Java中存在public,private,protected和default四种修饰符来实现封装和隔离。

在Kotlin中,也存在四种修饰符,分别是public,private,protected和internal,需要使用哪种修饰符时,直接在fun关键字前定义即可。

  • private:表示只对当前类内部可见
  • public:表示对所有类都可见,public是Kotlin中的默认项
  • protected:表示只对当前类和子类可见
  • internal:表示只对同一模块中的类可见

数据类和单例类

在项目开发中,数据类通常需要重写equals,hashCode,toString等方法,但其实这些代码具有极高的重复性。

而在Kotlin中,创建数据类可以直接使用如下代码:

package com.example.helloworld

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

fun main() 
    val Cellphone1 = Cellphone("HW", 5648.5)
    val Cellphone2 = Cellphone("HW", 5648.5)
    println(Cellphone1)
    println(Cellphone1 == Cellphone2)

在上面的代码中,使用data来声明一个类会自动生成equals,hashCode,toString等方法,以减少重复性的开发工作。

同时当一个类中没有任何代码时,可以省略末尾的大括号。

而在Kotlin中,创建单例类的方式也很简单,只需要将class关键字修改为object关键字即可:

package com.example.helloworld

object Singleton 
    fun singletonTest() 
        println("Test")
    

fun main() 
    Singleton.singletonTest()

上面的代码也就实现了单例模式,Kotlin会自动创建一个Singleton的实例,并保证全局只会存在一个Singleton实例。

Lambda

集合的创建与遍历

如果用户想要创建一个字符串列表的实例,可能需要构建ArrayList<string>,然后逐个添加字符串,但是这种方式可能比较麻烦,而Kotlin专门提供了内置的listOf函数来简化该过程:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    for(item in tmp) 
        println(item)
    

上面的代码就使用了listOf来创建字符串列表,然后通过for循环逐个进行打印。不过listOf函数创建的是一个不可变的集合,即只可用于读取,而无法修改。

而如果需要创建可以修改的集合,就需要使用mutableListOf函数:

package com.example.helloworld

fun main() 
    var tmp = mutableListOf("apple","banana","orange")
    tmp.add("pear")
    for(item in tmp) 
        println(item)
    

上面提到的是List的用法,而Set集合的用法大致类似,不过函数名换为了setOf和mutableSetOf。不过Set集合中存放的都是唯一元素,即不存在重复元素。

而Map的添加之前是先创建HashMap<key, value>的实例,然后将键值对数据添加到Map中,而Kotling同样可以使用map["key"] = value的形式直接赋值:

package com.example.helloworld

fun main() 
    var tmp = HashMap<String,Int>()
    tmp["apple"] = 10
    tmp["banana"] = 20
    tmp["orange"] = 30

    for ((key, value) in tmp) 
        println("key is " + key + ",value is " + value)
    

这样的写法看起来也有点麻烦,在Kotlin中还可以使用mapOf和mutableMapOf函数来简化该过程:

package com.example.helloworld

fun main() 
    var tmp = mapOf("apple" to 10, "banana" to 20, "orange" to 30)

    for ((key, value) in tmp) 
        println("key is " + key + ",value is " + value)
    

这样看起来就简洁多了。

集合的函数式API

这里主要看一下Lambda表达式的语法结构。

比如要查找列表中字符串最长的元素:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = ""

    for (item in tmp) 
        if (item.length > maxLengthFruit.length) 
            maxLengthFruit = item
        
    

    println("max length fruit is " + maxLengthFruit)

上面的代码可以实现,但是如果使用集合的函数式API,则有:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull  it.length 

    println("max length fruit is " + maxLengthFruit)

通俗来讲,Lambda是一小段可以作为参数传递的代码,其语法结构为:

param1:type,param2:type -> func

上面定义中:

  • param:表示传入参数
  • type:表示参数类型
  • func:表示函数体

比如上面的代码,其本质为:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var lambda = fruit:String -> fruit.length
    var maxLengthFruit = tmp.maxByOrNull(lambda)

    println("max length fruit is " + maxLengthFruit)

即maxByOrNull是一个普通的函数,其可接收一个Lambda形式的参数,并且在遍历集合时将每次遍历的值作为参数传递到Lambda表达式,然后找到其最大值。

上面的代码当然也可以这样写:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull(fruit:String -> fruit.length)

    println("max length fruit is " + maxLengthFruit)

而Kotlin规定,当Lambda参数式函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull()fruit:String -> fruit.length

    println("max length fruit is " + maxLengthFruit)

而如果Lambda表达式是函数唯一的参数,可以将函数括号省略:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNullfruit:String -> fruit.length

    println("max length fruit is " + maxLengthFruit)

而Kotlin又存在类型推导机制,因此Lambda表达式中的参数列表多数情况下可以不必声明参数类型:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNullfruit -> fruit.length

    println("max length fruit is " + maxLengthFruit)

而当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字代替:

package com.example.helloworld

fun main() 
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNullit.length

    println("max length fruit is " + maxLengthFruit)

这样也就形成了最初的形式。

这里再看几个其它的API:

package com.example.helloworld

fun main() 
    println(listOf("apple","banana","orange").mapit.uppercase())
    println(listOf("apple","banana","orange").filterit.length > 5)
    println(listOf("apple","banana","orange").anyit.length > 5)
    println(listOf("apple","banana","orange").allit.length > 5)
  • map:用来将列表进行映射转换
  • filter:用来对列表进行条件过滤
  • any:判断列表是否存在元素满足条件
  • all:判断列表是否全部元素满足条件

Java函数式API的使用

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

比如Java原生API中有一个常见的单抽象方法接口Runnable,该接口存在一个待实现的run方法:

public interface Runnable 
    void run();

而之前提到,对于任何一个Java方法,只要其接收Runnable参数,就可以使用函数式API。而Runnable主要是结合线程一块使用的,这里通过Java的线程类Thread来看一下:

new Thread(new Runnable() 
    @Override
    public void run() 
        System.out.println("Thread is running!")
    
).start();

上面采用了匿名类的写法,创建了Runnable接口的匿名类实例,并将之传递给Thread类的构造方法,然后调用Thread类的start方法执行该线程。

而写成Kotlin代码,则是:

package com.example.helloworld

fun main() 
    Thread(object :Runnable 
        override fun run() 
            println("Thread if running")
        
    ).start()

上面代码的形式类似,不过写法有些许差异,这是因为Kotlin舍弃了new关键字,因此创建匿名类就改用了object关键字。而符合Java函数式API使用条件,就可以对代码进行简化:

package com.example.helloworld

fun main() 
    Thread(Runnable 
        println("Thread if running")
    ).start()

上面的代码,Kotlin会明白Runnable后面的Lambda表达式就是要在run方法中实现的内容,同时如果Java方法的参数列表有且仅有一个Java单抽象方法接口函数,便可以对接口名进行省略:

package com.example.helloworld

fun main() 
    Thread println("Thread if running") .start()

这样的话,Android中常用的点击事件接口OnClickListener,原始形式为:

button.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        
    
);

就可以简化为:

button.setOnClickListener ;

空指针检查

可空类型检查

先看一段之前的代码:

fun doStudy(study: Study) 
    study.readBook()
    study.doHomework()

上面的代码并没有进行判空处理。

实际开发中,Java代码的大部分的方法都需要首先进行判空处理才可以保证后续的处理正常进行。而这样的检查会导致代码编写较为繁琐。而Kotlin利用编译时判空检查几乎能够杜绝空指针异常,

还是上面的代码,虽然形式上和Java代码并没有什么区别,但实际上是没有空指针风险的,因为Kotlin默认所有的参数和变量都不可为空,即上面传入的参数study一定不为空,因此上面的函数便可以正常调用。

即Kotlin将空指针异常的检查提前到了编译时期,即在编译期间存在空指针异常风险,会导致编译报错,而不是将错误推迟到代码执行期间,这样能保证在程序运行期间不会出现空指针异常。

而如果所有的参数和变量都不可为空,那么确实需要某个参数或变量为空时,就需要Kotlin提供的另外一套可为空的类型系统。

这种类型系统就是在类名后加上一个问号,比如上面的代码中,在Study后加上问号就表示此时参数study可以为空,不过此时要做判空处理:

fun doStudy(study: Study?) 
    if (study != null) 
        study.readBook()
        study.doHomework()
    

判空辅助工具

在判空处理中,如果使用if处理,就会使代码变得比较繁琐,因此Kotlin提供了一系列辅助工具。

首先最常用的就是?.操作符,该操作符是当对象不为空时正常调用相应的方法,而当对象为空时则什么都不做:

fun doStudy(study: Study?) 
    study?.readBook()
    study?.doHomework()

 还有一个是?:操作符,该操作符左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果:

fun getTextLength(text: String?):Int 
    if (text != null) 
        return text.length
     else 
        return 0
    

如果使用辅助判空工具,就可以写成:

fun getTextLength(text: String?) = text?.length?:0

可以说是相当简洁了。

再比如下面的代码:

package com.example.helloworld

var content: String?="hello"

fun printUpperCase() 
    val upperCase = content.toUpperCase()
    println(upperCase)


fun main() 
    if (content != null) 
        printUpperCase()
    


虽然在main函数中已经进行了判空处理,但是在printUpperCase中并不清楚外部已经进行该处理,因此还是无法编译通过。

此时如果想要强行通过编译,可以使用非空断言工具,写法是在对象后面加上!!:

fun printUpperCase() 
    val upperCase = content!!.toUpperCase()
    println(upperCase)

最后一个辅助工具是let,let既不是操作符,也不是关键字,而是一个函数,该函数提供了函数式API的编程接口,并将原始调用对象作为对象传递到Lambda表达式中,如:

obj.let obj2 -> func

上面的示例中,调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且该obj对象本身还会作为参数传递到Lambda表达式中。obj2只是更换了参数名,实际上和obj是同一个对象。

let函数和判空处理结合的操作为:

fun doStudy(study: Study?) 
    study?.let 
        it.readBook()
        it.doHomework()
    

这样就将let函数和判空处理结合起来了,而不用每个接口方法调用都是用?.操作符。

同时let函数是可以处理全局变量的判空问题,而if则会由于全局变量的值虽然会被其它线程更改而报错。

杂项

字符串内嵌表达式

Kotlin允许在字符串中嵌入$这中语法结构的表达式,并在运行时使用表达式执行的结果替代该内容:

"hello,$obj.name. nice to meet you"

而当表达式中仅有一个变量名时,还可将两侧的大括号省略:

"hello,$name. nice to meet you"

函数的参数默认值

之前提到了次构造函数,但其实次构造函数很少用,因为Kotlin能够为函数设定参数默认值。

fun printParams(num:Int, str:String = "hello") 
    println("num is $num, str is $str")

这样在调用的时候,就可以只传入num变量,而为str赋默认的值。

而如果调换默认值的位置:

fun printParams(num:Int = 10, str:String) 
    println("num is $num, str is $str")

此时调用时便不可只写一个参数,而要使用键值对的形式进行匹配,即:

printParams(str = "hello")

使用键值对的形式,便不用在意参数顺序的问题,因为是通过键值对匹配的,自然不会出现类型不匹配的问题。

这样默认的参数值在某些场景下便能够代替次构造函数的功能。

Android:Kotlin详细入门学习指南-基础语法

Android:Kotlin详细入门学习指南-基础语法(一)

本人也是在初学Kotlin,如有错误,请帮忙指出,持续更新

  • Kotlin被Google官方认为是Android开发的一级编程语言
  • 自 2019 年 Google I/O 以来,Kotlin 就成为了 Android 移动开发的首选。
  • 首先来看看Kotlin的基础语法

Kotlin是什么

  • Kotlin是Android开发的一级编程语言(Google官方认证)
  • 由JetBrains公司在2010年推出 & 开源,与Java语言互通 & 具备多种Java尚不支持的新特性
  • Android Studio3.0后的版本支持Kotlin

优点

  • 码更少、可读性更强 - 花更少的时间来编写代码与理解他人的代码
  • 成熟的语言与环境 - 自 2011 年创建以来,Kotlin 不仅通过语言而且通过强大的工具在整个生态系统中不断发展。 现在,它已无缝集成到 Android Studio 中, 并被许多公司积极用于开发 Android 应用程序。
  • Android Jetpack 与其他库中的 Kotlin 支持 - KTX 扩展 为现有的 Android 库添加了 Kotlin 语言特性,如协程、扩展函数、lambdas 与命名参数。
  • 与 Java 的互操作性 - 可以在应用程序中将 Kotlin 与 Java 编程语言一起使用, 而无需将所有代码迁移到 Kotlin。
  • 支持多平台开发 - 不仅可以使用 Kotlin 开发 Android,还可以开发 iOS、后端与 Web 应用程序。 享受在平台之间共享公共代码的好处。
  • 代码安全 - 更少的代码与更好的可读性导致更少的错误。Kotlin 编译器检测这些剩余的错误,从而使代码安全。
  • 易学易用 - Kotlin 非常易于学习,尤其是对于 Java 开发人员而言。
  • 大社区 - Kotlin 得到了社区的大力支持与许多贡献,该社区在全世界范围内都在增长。 根据 Google 的说法,Play 商店前 1000 个应用中有 60% 以上使用 Kotlin。

使用

1、点击Android Studio settings -> Plugins -> 搜索Kotlin Languages插件
2、在根目录的build.gradle中加入

buildscript 
    ext.kotlin_version = '1.3.61'
    repositories 
        mavenCentral()
    

    dependencies 
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    

3、在app/build.gradle中引入

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

buildscript 
    ext.kotlin_version = '1.3.61'
    dependencies 
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    

基本语法

定义包名 - 在源文件的开头定义包名:包名不必和文件夹路径一致:源文件可以放在任意位置。

package my.demo 
import java.util.
* //...

定义函数:定义一个函数接受两个 int 型参数,返回值为 int

fun sum(a: Int , b: Int) : Int 
	return a + b 


fun main(args: Array<String>) 
 	print("sum of 3 and 5 is ")
  	println(sum(3, 5)) 
  

该函数只有一个表达式函数体以及一个自推导型的返回值:

fun sum(a: Int, b: Int) = a + b 
fun main(args: Array<String>) 
 	println("sum of 19 and 23 is $sum(19, 23)") 
 

返回一个没有意义的值:
Unit 的返回类型可以省略

fun printSum(a: Int, b: Int): Unit  
	println("sum of $a and $b is $a + b")
	 
fun main(args: Array<String>)  
printSum(-1, 8)
	 

定义局部变量
声明常量:

fun main(args: Array<String>) 
	 val a: Int = 1 // 立即初始化 
	 val b = 2 // 推导出Int型 
	 val c: Int // 当没有初始化值时必须声明类型 
	 c = 3 // 赋值 
	 println("a = $a, b = $b, c = $c")
  

变量:

fun main(args: Array<String>) 
	 var x = 5 // 推导出Int类型 
	 x += 1 println("x = $x")
 

注释:
与 java 和 javaScript 一样,Kotlin 支持单行注释和块注释。

// 单行注释
/* 哈哈哈哈 这是块注释 */

与 java 不同的是 Kotlin 的 块注释可以级联。
使用字符串模板

fun main(args: Array<String>) 
	 var a = 1 // 使用变量名作为模板: 
	 val s1 = "a is $a" a = 2 // 使用表达式作为模板:
	 val s2 = "$s1.replace("is", "was"), but now is $a" println(s2)
 

使用条件表达式

fun maxOf(a: Int, b: Int): Int  
	if (a > b) 
	 return a 
	  else  
	 return b
	   
 
 fun main(args: Array<String>)  
 	println("max of 0 and 42 is $maxOf(0, 42)") 
 

把if当表达式:

fun maxOf(a: Int, b: Int) = if (a > b) a else b
fun main(args: Array<String>) 
	println("max of 0 and 42 is $maxOf(0, 42)")

使用可空变量以及空值检查
当空值可能出现时应该明确指出该引用可空。
下面的函数是当 str 中不包含整数时返回空:

fun parseInt(str : String): Int? //... 

使用值检查并自动转换
使用 is 操作符检查一个表达式是否是某个类型的实例。如果对不可变的局部变量或 属性进行过了类型检查,就没有必要明确转换:

fun getStringLength(obj: Any): Int? 
 if (obj is String)  
 // obj 将会在这个分支中自动转换为 String 类型 return obj.length 
 // obj 在种类检查外仍然是 Any 类型 return null
 

使用循环

fun main(args: Array<String>) 
	 val items = listOf("apple", "banana", "kiwi") 
	 for (item in items) 
	  println(item) 
	  

或者这样:

fun main(args: Array<String>)  
	val items = listOf("apple", "banana", "kiwi") 
	for (index in items.indices) 
	 println("item at $index is $items[index]") 
	  
 

使用 while 循环

fun main(args: Array<String>)  
	 val items = listOf("apple", "banana", "kiwi")
	 var index = 0 while (index < items.size)  
	 println("item at $index is $items[index]")
	  index++
	  

使用 when 表达式

fun describe(obj: Any): String = 
	when (obj) 
	 1 -> "One" 
	 "Hello" ->"Greeting" 
	  is Long -> "Long" 
	  !is String -> "Not a string" 
	  else -> "Unknown" 
  
  fun main(args: Array<String>) 
	     println(describe(1)) println(describe("Hello"))
	     println(describe(1000L))
	     println(describe(2)) 
	     println(describe("other"))
 

使用ranges
使用 in 操作符检查数值是否在某个范围内:

fun main(args: Array<String>)  
	val x = 10 
	val y = 9
	 if (x in 1..y+1) 
	  println("fits in range") 
	   
 

检查数值是否在范围外:

if (-1 !in 0..list.lastIndex)  
	println("-1 is out of range") 

使用步进

for (x in 1..10 step 2)
for (x in 9 downTo 0 step 3)

使用集合

对一个集合进行迭代

fun main(args: Array<String>)  
	val items = listOf("apple", "banana", "kiwi") 
	for (item in items) 
	 println(item) 
	  
 

使用 in 操作符检查集合中是否包含某个对象

fun main(args: Array<String>)  
	val items = setOf("apple", "banana", "kiwi") 
	when  
	"orange" in items -> println("juicy") 
	"apple" in items -> println("apple is fine too") 
	 

使用lambda表达式过滤和映射集合

fun main(args: Array<String>)  
	val fruits = listOf("banana", "avocado", "apple", "kiwi") 
	fruits 
	.filter  it.startsWith("a")  
	.sortedBy  it  
	.map  it.toUpperCase()  
	.forEach  println(it) 
 

习惯用语

创建DTOs(POJOs/POCOs) 数据类
相当于java的Bean,

data class Customer(val name: String, val email: String)

给 Customer 类提供如下方法
-为所有属性添加 getters ,如果为 var 类型同时添加 setters – equals() - - haseCode() – toString() – copy()

函数默认值

fun foo(a: Int = 0, b: String = "") ...

过滤 list

val positives = list.filter  x -> x >0 

或者更短:

val positives = list.filter  it > 0 

字符串插值

println("Name $name")

实例检查

when (x)  
	is Foo -> ... 
	is Bar -> ... 
	else -> ... 

遍历 map/list

for ((k, v) in map) 
	 print("$k -> $v") 

k,v 可以随便命名
使用 ranges

for (i in 1..100)  ...  // 闭区间: 包括100 
for (i in 1 until 100)  ...  // 半开区间: 不包括100 
for (x in 2..10 step 2)  ...  
for (x in 10 downTo 1)  ...  
if (x in 1..10)  ...  
for (i in 1..100)  ...  
for (i in 2..10)  ... 

只读 list

val list = listOf("a", "b", "c")

只读map

val map = mapOf("a" to 1, "b" to 2, "c" to 3)

访问 map

println(map["key"]) 
map["key"] = value

懒属性(延迟加载)

val p: String by lazy  // 生成string的值 

扩展函数

fun String.spcaceToCamelCase()  ...  
"Convert this to camelcase".spcaceToCamelCase()

创建单例模式

object Resource 
 	val name = "Name"

如果不为空则… 的简写

val files = File("Test").listFiles() 
println(files?.size)

如果不为空…否则… 的简写

val files = File("test").listFiles() 
println(files?.size ?: "empty")

如果声明为空执行某操作

val data = ... 
val email = data["email"] ?: throw IllegalStateException("Email is missing!")

如果不为空执行某操作

val date = ... 
data?.let ...//如果不为空执行该语句块 

返回 when 判断

fun transform(color: String): Int 
	 return when(color)  
	 "Red" -> 0 "Green" -> 1 
	 "Blue" -> 2 
	 else -> throw IllegalArgumentException("Invalid color pa ram value")
	   

try-catch 表达式

fun test() 
	 val result = try 
	  count() 
	  catch (e: ArithmeticException) 
	   throw IllegaStateException(e)
	  //处理 result

if 表达式

fun foo(param: Int) 
	val result = if (param == 1)  
	"one"
	  else if (param == 2)  
	 "two" 
	  else 
	  "three"
	   

需要泛型信息的泛型函数的方便形式

// public final class Gson  
// ... 
// public <T> T fromJson(JsonElement json, Class<T> classOfT ) throws JsonSyntaxException  
// ... inline 
fun <reified T: Any> Gson.fromJson(json): T = this.fromJs on(json, T::class.java)

命名风格

如有疑惑,默认为Java编码约定,比如:
使用骆

  • 驼命名法(在命名中避免下划线)
  • 类型名称首字母大写
  • 方法和属性首字母小写
  • 缩进用四个空格
  • public 方法要写说明文档,这样它就可以出现在 Kotllin Doc 中

冒号

在冒号区分类型和父类型中要有空格,在实例和类型之间是没有空格的:

interface Foo<out T : Any> : Bar 
	fun foo(a: Int): T

Lambdas

在 Lambdas 表达式中,大括号与表达式间要有空格,箭头与参数和函数体间要有 空格。尽可能的把 lambda 放在括号外面传入

list.filter  it > 10 .map  element -> element * 2 

在使用简短而非嵌套的lambda中,建议使用 it 而不是显式地声明参数。在使用 参数的嵌套lambda中,参数应该总是显式声明

类声明格式

参数比较少的类可以用一行表示:

class Person(id: Int, name: String)

具有较多的参数的类应该格式化成每个构造函数的参数都位于与缩进的单独行中。 此外,结束括号应该在新行上。如果我们使用继承,那么超类构造函数调用或实现 的接口列表应该位于与括号相同的行中

class Person( 
	id: Int, 
	name: String, 
	surname: String 
	) : Human(id, name) 
	 // ... 

对于多个接口,应该首先定位超类构造函数调用,然后每个接口应该位于不同的行 中

class Person( 
	id: Int, 
	name: String, 
	surname: String 
	) : Human(id, name), 
	KotlinMaker  // ... 

构造函数参数可以使用常规缩进或连续缩进(双倍正常缩进)。

Unit

如果函数返回 Unit ,返回类型应该省略:

fun foo()  // ": Unit"被省略了 

函数 vs 属性

在某些情况下,没有参数的函数可以与只读属性互换。尽管语义是相似的,但是有 一些风格上的约定在什么时候更偏向于另一个。
在下面的情况下,更偏向于属性而不是一个函数:

  • 不需要抛出异常 – 拥有O(1)复杂度 – 低消耗的计算(或首次运行结果会被缓 存) – 返回与调用相同的结果

本人也是在初学Kotlin,如有错误,请帮忙指出,持续更新

以上是关于Android开发基础——Kotlin简介的主要内容,如果未能解决你的问题,请参考以下文章

Google 如何看待 Kotlin 与 Android

Kotlin基础从入门到进阶系列讲解(进阶篇)Jetpack,(更新中)

Android:Kotlin详细入门学习指南-基础语法

使用Kotlin开发Android应用

Android开发学习之路--Kotlin之基础语法

Kotlin的android扩展:对findViewById说再见(KAD 04)