使用Kotlin开发Android(一)

前言

为什么要去学习Kotlin来开发 Android?“Google 在 Google I/O 2017 宣布支持 Kotlin 为 Android”这一句大概足以让我们开发者去尝试这门新语言了吧。Kotlin是一种基于jvm的静态类型编程语言,由 JetBrains 开发。

Kotlin有哪些好处呢?

  1. 相比java更简洁~短小精悍~(略略略),代码行数减少约40%。 它也更安全,NullPointerException算是大家见过最多的异常了吧,而kotlin恰恰可以干掉它,就问你开不开森
  2. 扩展函数,智能类型转换,不用再见到那该死的findViewById了
  3. 无缝对接Java,可以java代码与kotlin代码互相调用
  4. 一键java转kotlin,简化现有代码的迁移
  5. 可进行android开发,服务器端开发,web开发,桌面开发等

语法

基础语法

变量
  • 声明变量使用var,声明常量使用val,声明时kotlin语言是可以自动推断出字段类型的.
fun main(args: Array<String> {
var quantitly  = 10
val price: Double = 20.3
val name: String = "大米"
println("单价:$price")
println("数量:$quantity")
println("产品:$name 总计:${quantity * price}")
语句
  • in关键字的使用: 判断一个对象是否在某一个区间内
//如果存在于区间(1, y-1)
if(x in 1..y-1)
    print("OK")
//如果x不存在于array中
if(x !in 0..array.lastIndex)
//打印1到5
for(x in 1..5)
//遍历集合
for(name in names) {
    println(name);
}
//如果names集合中包含text对象
if(text in names)
  • when表达式: 类似于Java中的switch,但kotlin更加智能,可以自动判断参数的类型.
fun cases(obj: Any) {
    when(obj) {
        1 -> print("第一项")
        "hello" -> print("这个是字符串")
        is Long -> print("这是一个Long类型的数据")
        !is String -> print("这不是String类型的数据")
        else -> print("else类似于Java中的default")
    }
}
  • 智能类型推测: 判断一个对象是否为一个类的实例, 可以使用is关键字.与Java中的instanceof关键字类似,但在kotlin中如果已经确定了一个对象的类型,可以在接下来的代码块中直接作为这个确定类型使用.
fun getStringLength(obj: Any): Int? {
    if(obj is String) {
        //做过类型判断以后,obj会被系统自动转换为String类型
        return obj.length
    }
    //同时还可以使用!is, 来取反
    if(obj !is String) {
    }
    //代码块外部的obj仍然是Any类型的引用
    return null
}
  • !!操作符: 将任何值转换为非空类型,若该值为空则抛出异常.如下所示:

    var a = null
    a!!
    //运行代码,抛出KotlinNullPointerException
    
空安全
  1. 可空类型与非空类型

    • 在Kotlin中,类型系统区分一个引用可以容纳null(可空引用)还是不能容纳(非空引用).例如,String类型的常规变量不能容纳null.

      var a = "a"
      a = null
      
    • 如果要允许为空,我们可以声明一个变量为可空字符串,在字符串类型后面加一个问号?,写作String?,如下所示:

      var b: String? = "b"
      b = null
      
  2. 安全调用操作符

    • 接着上面的代码,如果你调用a的方法或者访问它的属性,不会出现NullPointerException,但如果调用b的方法或者访问它的属性,编译器会报告一个错误.

      b.length
      
    • 这个时候可以使用安全调用操作符,写作?.,在b后面加安全调用操作符,表示如果b不为null则调用b.length,如下所示:

      b?.length
      
    • 安全调用操作符还能链式调用,如果该链式调用中任何一个属性为null,整个表达式都会返回null.如果要只对非空值执行某个操作,安全调用操作符可以与let一起使用

      val listWithNulls: List<String?> = listOf("A", null, "B")
      for (item in listWithNulls) {
          item?.let { println(item) }
      }
      
函数
  • 函数的声明: 函数使用关键字fun声明.
fun say(str: String): String {
    return str
}    
//可以简写为:
fun say(str: String): String = str
//如果是返回Int类型,那么可以不写返回类型
fun getIntValue(value: Int) = value
  • 函数的默认参数:
//你可以调用say(),来得到默认的字符串"hello",也可以自己传入参数say("world")来得到传入参数值.
fun say(str: String = "hello"): String = str
//多行参数的写法:
fun say(firstStr: String = "Hello", lastStr: String = "World") {
}
  • 可变参数
//在Java中,我们可以表示一个可变参数
public boolean hasEmpty(String... strArray) {
    for(String str: strArray) {
        if("".equals(str) || str == null) 
            return true;
    }
    return false;
}
//在kotlin中,使用关键字vararg来表示
fun hasEmpty(vararg strArray: String?): Boolean {
    for(str in strArray) {
        if("".equals(str) || str == null)
            return true
    }
    return false
}
  • 单表达式函数
//函数体内只有一条语句,且有返回值
fun foo(): String {
    return "abc"
}
//这时可以省略大括号,变成单表达式函数
fun foo() = "abc"
  • 扩展函数: 你可以给父类添加一个方法,这个方法将可以在所有子类中使用.
fun Activity.toast(message: CharSequence, duration: Int = Toas.lENGTH_SHORT) {
}
  • 将函数作为参数
fun lock<T>(lock: Lock, body: () ->T) : T(lock.lock()     try {return body()}

Kotlin与Java混编

将Java转为Kotlin
  • 使用kotlin插件
在Kotlin中调用Java代码
  • 如果一个Java方法返回void, 对应的在Kotlin代码中它将返回Unit.在kotlin中可以省略这个返回类型.
  • Java中有static关键字,在kotlin中没有这个关键字,你需要使用@JvmStatic替代这个关键字.同样,在kotlin中也有很多的关键字是Java中没有的.例如in,is,data等.如果Java中使用了这些关键字,需要加上反引号(`)转义来避免冲突.

    //java代码中有个方法叫 is()
    public void is() {
    }
    //转换为kotlin代码需要加反引号转义
    fun `is`() {
    }
    
在Java中调用Kotlin代码
  • 如果想在Java中通过类名调用一个kotlin类的方法,你需要给这个方法加入@JvmStatic注解(这个注解只在jvm平台有用).否则你必须通过对象调用这个方法.
StringUtils.isEmpty("hello");
StringUtils.INSTANCE.isEmpty2("hello");
//
object StringUtils {
    @JvmStatic fun isEmpty(str: String): Boolean {
        return "" == str
    }
    fun isEmpty2(str: String):Boolean {
        return "" == str
    }
}



class StringUtils {
    companion object {
        fun isEmpty(str: String): Boolean {
            return "" == str
        }
    }
}
  • companion object表示外部类的一个伴生对象,你可以理解为外部类自动创建了一个对象作为自己的field. Java在调用时,可以这样写:StringUtils.isEmpty().

Kotlin的类特性

构造函数
  • Kotlin的构造函数可以写在类头中,跟在类名后面,如果有注解还需要加上关键字construtor.这种写法声明的构造函数,我们称之为主构造函数.例如下面我们为Person创建带一个String类型参数的构造函数.

    class Person(private val name: String) {
        fun sayHello() {
            println("hello $name")
        }
    }
    
  • 在构造函数中声明的参数,它们默认属于类的公有字段,可以直接使用,如果你不希望别的类访问到这个变量,可以用private修饰.在主构造函数中不能有任何代码实现,如果有额外的代码需要在构造方法中执行,你需要放到init代码块中执行.同时,在本示例中由于需要更改name参数的值,我们将val改为var,表明name参数是一个可改变的参数.

class Person(private var name: String) {
    init {
        name = "HeHong Liu"
    }
    internal fun sayHello() {
        println("hello $name")
    }
}
  • 如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数.构造函数的可见性是public.如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的主构造函数.另外,在JVM上,如果主构造函数的所有的参数都有参数值,编译器会生成一个额外的无参构函数,它将使用默认值.
次级构造函数
  • 一个类当然会有多个构造函数的可能,只有主构造函数可以写在类头中,其他的次级构造函数就要写在类体中了.
class Person(private var name: String) {
    private var description: String? = null
    init {
        name = "HeHong Liu"
    }
    constructor(name: String, description: String) : this(name) {
        this.description = description
    }
    internal fun sayHello() {
        println("hello $name")
    }
}
  • 这里我们让次级构造函数调用了主构造函数,完成name的赋值.由于次级构造函数不能直接将参数转换为字段,所以需要手动声明一个description字段,并为description字段赋值.
修饰符
  • open: Kotlin默认会为每个变量和方法添加final修饰符.这么做的目的是为了程序运行的性能.为每个类加了final也就是说,在kotlin中默认每个类都是不可被继承的.如果你确定这个类是会被继承,那么你需要给这个类添加open修饰符.接口中的属性和方法都是open的,不用另外加open标识.
  • internal: 在Kotlin中,默认的访问权限是public, 也就是说不加访问权限修饰符的就是public的.而多增加了一种访问修饰符叫internal,它是模块级别的访问权限.何为模块,我们称被一起编译的一系列Kotlin文件为一个模块.在IDEA中可以很明确的看到一个module就是一个模块,当跨module的时候就无法访问另一个moduleinternal变量或方法.
  • private: 本类内部(包含其所有成员)都可见.
  • protected: 只在本类内部+子类中可见.
  • public: 能见到类声明的任何客户端都可以见到其public成员.
field标识符
  • 如果不想定义一个字段来保存属性的值,可以使用field标识符,如下所示:
class User {
    var username: String = "LLhon"
        get() = field
        set(value) {
            field = value
        }
}
嵌套类
  • inner关键字标记嵌套类, 可以通过外部类的实例调用嵌套类.
class User {
    var age: Int = 0
    inner class UserName {
    }
}
var userName: User.UserName = User().UserName()
一些特殊的类
  • 枚举类
  • sealed密封类
  • data数据类: data修饰的类称之为数据类.它通常用在我们写的一些POJO类上.当data修饰后,会自动将所有成员用operator声明,即为这些成员生成类似Java的getter/setter方法.

类的扩展

扩展方法
  • 在Java开发的时候,经常会写一大堆的Utils类.如果每个类在想要用这些工具类的时候,他们自己就已经具备了这些工具方法多好,Kotlin的类扩展方法就是这个作用.
    fun Activity.toast(msg: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
        Toast.makeText(this, msg, duration).show()
    }


>首先是一个`fun`关键字,紧接着是要扩展哪个类的类名,点方法名,然后是方法的声明和返回值以及方法体. 
强转与智能转换
  • 在Kotlin中,用is来判断一个对象是否是某个类的实例,用as来做强转.Kotlin有一个很好的特性,叫智能转换.就是当已经确定一个对象的类型后,可以自动识别为这个类的对象,而不用再手动强转.
fun main(args: Array<String>) {
    var animal: Animal? = xxx
    if(animal is Dog) {
        //在这里animal被当作Dog的对象来处理
        animal.bark()
    }
}
伴生对象
  • 由于Kotlin没有静态方法.在大多数情况下,官方建议是简单地使用包级函数.如果你需要写一个可以无需用一个类的实例来调用,但需要访问类内部的函数(例如:工厂方法或单例),你可以把它写成一个用companion修饰的对象内的方法.
class StringUtils {
    companion object {
        fun isEmpty(str: String): Boolean {
            return "" == str
        }
    }
}
单例类设计
  • 伴生对象更多的用途是用来创建一个单例类.如果只是简单的写,直接用伴生对象返回一个val修饰的外部类对象就可以了,但是更多的时候我们希望在类被调用的时候才去初始化他的对象.以下代码将线程安全问题交给虚拟机在静态内部类加载时处理,是一种推荐的写法:
class Single private constructor() {
    companion object {
        fun get(): Single{
            return Holder.instance
        }
    }

    private object Holder {
        val instance = Single()
    }
}
  • 使用伴生对象实现静态类成员的功能,如下所示:
//Java写法
class User {
    static User instance = new User();
    public void printlnUser() {
    }
}
User.instance.printlnUser()
//Kotlin写法
class User {
    companion object {
        var instance = User()
    }
    fun printlnUser {
    }
}
User.instance.printlnUser()

函数与闭包

Unit
  • 如果一个函数是空函数,比如Android开发中的TextWatch接口,通常只会用到一个方法,但必须把所有方法都重写一遍,就可以通过这种方式来简写.
    editText.addTextChangedListener(object: TextWatcher {
        override fun afterTextChanged(s: Editable?) = Unit
        override fun beforeTextChanged(s: CharSequence?,start: Int,count: Int, after: Int) = Unit
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
    })


> Unit表示的是一个值的类型,这种类型类似于Java中的`void`类型
Nothing
  • Nothing也是一个值的类型.如果一个函数不会返回(也就是说只要调用这个函数,那么在它返回之前程序肯定出错了,比如一定会抛出异常的函数),理论上你可以随便给他一个返回值,通常我们会声明为返回Nothing类型.
闭包
  • 以下代码,if语句仍旧是一个闭包.但通常情况下,我们所说的闭包是Lambda表达式.
    fun main(args: Array<String>) {
        test
    }
    val test = if (5 > 3) {
        print("yes")
    } else {
        print("no")
    }

> 注意: 闭包是不能有变长参数的.
自执行闭包
  • 自执行闭包就是在定义闭包的同时直接执行闭包,一般用于初始化上下文环境.例如:
{x: Int, y: Int ->
    println("${x + y}")
}(1, 3)
Lambda表达式
  • 以下代码首先声明了一个名为printMsg的Lambda,它接受一个String类型的值作为参数,然后在main函数中调用它.如果还想省略,你还可以在调用时直接省略invoke,像函数一样使用.
val printMsg = {msg: String ->
    println(msg)
}
fun main(args: Array<String>) {
    printMsg.invoke("hello")
}
//更简洁的写法
fun main(args: Array<String>) {
    printMsg("hello")
}
  • Lambda表达式还有非常多的语法糖,比如
    • 当参数只有一个的时候,声明中可以不用显示声明参数,在使用参数时可以用it来替代那个唯一的参数.
    • 当有多个用不到的参数时,可以用下划线来替代参数名(1.1以后的特性),但是如果已经用下划线来省略参数时,是不能使用it来替代当前参数的.
    • Lambda最后一条语句的执行结果表示这个Lambda的返回值.
高阶函数
  • Lambda表达式最大的特点是可以作为参数传递.当定义一个闭包作为参数的函数,称这个函数为高阶函数.
fun main(args: Array<String>) {
    log("world", printMsg)
}
val printMsg = {str: String ->
    println(str)
}
val log = {str: String, printLog: (String) -> Unit ->
    printLog(str)
}
内联函数

集合泛型与操作符

Kotlin中的集合接口
  • 所有类声明的泛型尖括号里面如果加入了out关键字,则说明这个类的对象是只读的,例如它只有:get(),size()等方法,而没有set(),remove()等方法.相反地,如果没有加out关键字,或者换一种记法:如果开头是MutableXXX那就是一个跟Java中用法一致的集合类.
in与out
  • out对应的,还有一个in.表示泛型参数是只写的.泛型的in关键字一般用在我们定义的代码中.在看下面这个demo之前,你需要知道一点:Kotlin的泛型是支持协变的.例如下面这段代码:
open class A
open class B: A()
open class B; A啊
val mutableList: MutableList<B> = mutableListOf(B(), B(), C())
val list: List<A> = mutableList;
  • 在Kotlin中,可以直接把List<C>的对象赋值给List<A>.泛型声明中,用in声明泛型参数,表示这个参数只能是入参,不能对外返回这个参数
    open class A
    open class B: A()
    open class C: B()
    class TypeArray<in A> {
        //in修饰了A,表示A是可以作为参数的.
        fun getValue(a: A): Int? {
            return a?.hashCode()
        }
        //这段代码是非法的,因为A不能被返回
        fun getA(a: A): A? {
            return a
        }
    }

> 总结: 用out来修饰T,只能消费T类型,不能返回T类型,用in来修饰T,只能返回T类型,不能消费T类型.简单来说就是in是消费者,out是生产者.
集合的初始化
  • 在Kotlin中,集合类一般不使用构造方法去初始化,而是使用同一的入口方法,例如初始化一个MutableList,我们使用的是如下代码:
val mutableList = mutableListOf(0, 1, 2, 3)
  • 类似的初始化集合对象的方法还有:
//创建一个List<>对象
var list = listOf(0, 1, 2)
//创建一个Set<>对象
val ss = setOf(1, 2, 4)
操作符
  • 在Kotlin中,操作符本质上是方法调用.例如List的forEach的实现原理实际上是定义了一个这样的方法.为Iterable增加了一个扩展方法,叫forEach,它接收一个T作为参数,并返回Unit的闭包作为参数.
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

待更……(我也不知道会拖更到啥时候~o( =∩ω∩= )m)