Kotlin基础使用

166 阅读7分钟

什么是Kotlin?

Kotlin是一门编程语言,由JetBrains公司开发的,基于JVM,所以支持Kotlin和Java的混合编写进行Android开发。

为什么要用Kotlin

相信在座的各位在刚接触Android开发的时候用的一般都是Java语言,那为什么现在转向Kotlin呢?Goole推荐使用,而且现在越来越多的三方库都在使用kotlin开发,kotlin有很多特性,比如空指针安全、方法扩展、支持函数式编程、丰富的语法糖。相对于Java开发,kotlin更加简洁、优雅,提高开发效率。

日常使用

变量声明:var和 val

var 代表是可变的,val代表不可变即只读,比如

    val a : Int = 1 // 正常的声明、赋值
    val b = 2 // 类型推导,可以省略Int声明 
    /* 定义函数:自动推导函数的返回值类型 */
    fun sum(a: Int, b: Int) = a + b

在Java里,我们经常需要做很多判空,比如:

public void fun1 (String str){
   if(str != null) {
       System.out.println("Length = " + str.length());
   }
}

而在kotlin中,是这样的:

// 如果str=null,会打印:Length = null
fun fun1 (str: String?) {
    println("Length = " + str?.length) 
}

还有Java里面的强制转换,很容易抛异常ClassCastException,在Kotlin中,也可以通过安全的类型转换进行避免:

val l2 = listOf("A",1,3,6,8,'c')
l2.forEach { println(it as? Int) }

消失的 Getter 和 Setter

我们用Java写Bean类的时候,往往是这样定义的:

public class Person{
     private int id;
     private String name;
     //瞅啥瞅,省略掉的是 getter 和 setter!
     ...
 }

而在Kotlin中,是这样的:

data class Person(val id: Int, val name: String)

再见findViewById

通过kotlinx引入布局文件,直接使用View

伴生对象

伴生对象通过在类中使用 companion object 来创建,用来替代静态成员,类似于 Java 中的静态内部类。比如:

class CompanionKotlin {
   companion object {
      const val DATA = "CompanionKotlin_DATA"
   }
}

如果是Java调用kotlin的话,可以使用CompanionKotlin.INSTANCE.DATA,当然我们也可以使用注解来减少上述调用来减少字节码的生成,

对于基本类型和字符串,可以使用 const 关键字将常量声明为编译时常量。
对于公共字段,可以使用 @JvmField 注解。
对于其他类型的常量,最好在它们自己的主类对象而不是伴生对象中来存储公共的全局常量。

比如,

object Test1 {
   @JvmField
   val NAME = "nanchen"
   @JvmStatic
   fun getAge() = 18
}

Kotlin 继承与构造函数

继承:

//加上open关键字,就允许被继承了。
open class Person{}
//java中用extends继承;kotlin中用:冒号继承
class Student : Person(){}

构造函数

//主构造函数
class Student(val son: String, val grade: Int) : Person() {
	//init主构造函数体
    init {
        println("son is $son")
        println("grade is $grade")
    }
}
//次构造函数"constructor"关键字定义
//注意:所有次构造函数必须调用主构造函数
open class Person(name: String, age: Int) {}
class Student(val son: String, val grade: Int, name: String, age: Int) : Person(name, age) {
    constructor(name: String, age: Int) : this("", 0, name, age) {}
    constructor() : this("", 0) {}
}

Kotlin ?和!!

"?"当前对象可以为空,系统在任何情况不会报它的空指针异常。
"!!"当前对象不能为空,如果对象为null,那么系统一定会报异常!

常用操作符

内联函数 run, with, let, also, apply, takeIf

let
//let 表示object不为null的条件下,才会去执行let函数体
object?.let{
   it.todo()
}
with
//with 适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可
with(object){
	//todo
}
run
//run 实际上可以说是let和with两个函数的结合体
run(object){
	//todo
}
apply
//apply 从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身
apply(object){
	//todo
}
also
//also also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。而also函数返回的则是传入对象的本身
object.also{
	//todo
}

when

kotlin中使用when来代替switch,比如
// 结合着 in 和 when 一起使用
when (input) {
  in Regex("[09]") -> println("contains a number")
  in Regex("[a-zA-Z]") -> println("contains a letter")
}

takeIf

// Original code
if (someObject != null && someObject.status) {
   doThis()
}
// Better code
if (someObject?.status == true) {
   doThis()
}
// Improved code
someObject?.takeIf{ it.status }?.apply{ doThis() }

Kotlin Java中调用Kotlin时,如何使用默认值省略函数参数?

常规操作

//kotlin
class OrderActivity : BaseKtActivity() {
	companion object {
	    fun startIntent(orderId: String, orderType: Int = 0) {
			//TODO 逻辑处理
	    }
    }
}
//java调用
Orderctivity.Companion.startIntent("orderId",0);

添加@JvmOverloads后

class OrderActivity : BaseKtActivity() {
	companion object {
		@JvmOverloads
	    fun startIntent(orderId: String, orderType: Int = 0) {
			//TODO 逻辑处理
	    }
    }
}
//java调用
Orderctivity.Companion.startIntent("orderId");
//OR
Orderctivity.Companion.startIntent("orderId",0);

by lazy 和 lateinit 延迟初始化

在 Android 开发中,我们经常会有不少的成员变量需要在 onCreate() 中对其进行初始化,特别是我们在 XML 中使用的各种控件,而 Kotlin 要求声明成员变量的时候默认需要为它声明一个初始值。这时候就会出现不少的下面这样的代码。

private var textView:TextView? = null

迫于压力,我们不能不为这些 View 加上 ? 代表它们可以为空,然后为它们赋值为 null。实际上,我们在使用中一点都不希望它们为空。这样造成的后果就是,我们每次要使用它的时候都必须去先判断它不为空。好在 Kotlin 推出了 lateinit 关键字:延迟加载。这样我们可以先绕过 kotlin 的强制要求,在后面使用的时候,再也不需要先判断它是否为空了。但要注意,访问未初始化的 lateinit 属性会导致UninitializedPropertyAccessException。

/** lateinit var **/
private lateinit var name: String
 
/** by lazy **/
//用于属性延迟初始化
val name: Int by lazy { 1 }
 
//用于局部变量延迟初始化
public fun foo() {
    val bar by lazy { "hello" }
    println(bar)
    //::object.isInitialized可用于判断object变量是否已经初始化
    if(::name.isInitialized){}
}

Kotlin 集合

List接口的方法支持读功能 MutableList接口的方法支持读写功能

//List集合
//创建不可变集合,无法对该集合进行添加,修改,删除操作。
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
//创建可变集合
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
    println(fruit)
}
//Map集合
val map = mapOf<String, Int>("Apple" to 1, "Banana" to 2, "Orange" to 3)
for ((fruit, number) in map) {
    println("fruit is " + fruit + ", number is " + number)
}
//OR
val map = HashMap<String, String>()
	map["Apple"] = "Apple"
	map["Banana"] = "Banana"
	map["Orange"] = "Orange"
//ArrayList集合
var list = ArrayList<String>()
    list.add("Apple")
    list.add("Banana")
    list.add("Orange")
    list.add("Pear")
    list.add("Grape")
    list.add("Watermelon")

kotlin的单例实现

1.使用 Object 实现单例
object SingletonDemo {}

此种方法相当于Java中的饿汉单例

2.使用 by lazy 实现单例
class SingletonDemo private constructor() {
    companion object {
        val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        SingletonDemo() }
    }
}

此种方法相当于Java里的双重检测单例

3.使用静态内部类实现
class SingletonDemo private constructor() {
    companion object {
        val instance = SingletonHolder.holder
    }

    private object SingletonHolder {
        val holder= SingletonDemo()
    }

}

函数、属性扩展

扩展函数相当于在本类生成了一个静态方法即publick static final格式,使用扩展函数可以省略很多Utils的编写,比如:

// 函数扩展
fun String.firstChar() : String? {
    return if(this.length > 0) this.substring(0,1) else "NULL"
}

这个扩展函数可以在任何地方声明,然后其他任何地方String对象就都可以使用这个方法了:

val s = "Hello".firstChar())  // s的值为"H"
println("你好".firstChar())   // 打印: 你
println("".firstChar())   // 打印: NULL

兼容Java

Java和Kotlin可以混合开发,逐步替换,其实Kotlin变异之后一样是class文件。

语法糖

判断 View 是否在屏幕上

val View.inScreen: Boolean
    get() {
        // 获取屏幕宽度
        val screenWidth = context?.resources?.displayMetrics?.widthPixels ?: 0
        // 获取屏幕高度
        val screenHeight = context?.resources?.displayMetrics?.heightPixels ?: 0
        // 构建屏幕矩形
        val screenRect = Rect(0, 0, screenWidth, screenHeight)
        val array = IntArray(2)
        // 获取视图矩形
        getLocationOnScreen(array)
        val viewRect = Rect(array[0], array[1], array[0] + width, array[1] + height)
        // 判断屏幕和视图矩形是否有交集
        return screenRect.intersect(viewRect)
    }

为View增加一个扩展方法,返回布尔值,在其中获取视图和屏幕矩形区域,然后判断是否有交集,若有则表示视图出现在屏幕上。

将 px 值转换成 dp 值

在非 xml 环境下构建布局,需要将 px 转换为 dp 来进行多屏幕适配。

val Int.dp: Int
    get() {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            this.toFloat(),
            Resources.getSystem().displayMetrics
        ).toInt()
    }

为 Int 扩展一个属性dp,它的类型是 Int。在get()中定义该属性的取值算法。 然后就可以像这样动态地将 Int 值 dp 化:

viewGroup.addView( textView, LayoutParam( 40.dp, 50.dp ) )

高阶函数

通俗的说和数学里面的高阶函数概念类似,也就是函数里面的参数可以是函数。当然返回值也可以是函数。

1.先看看平时使用比较多的内置高阶函数 用kotlin写view的onClickListener
 tV.setOnClickListener {
            //doSomeThing
        }

里面的lamba表达式就是一个函数 再看看集合里面的filter、map

listOf(1, 2, 3)
            .filter { it > 2 }
            .map { it + 5 }

/**
 * Returns a list containing only elements matching the given [predicate].
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

filter、map的参数都是一个lambda函数