一、基础语法
Kotlin中可变变量、只读变量、静态变量、常量
格式:修饰符  名称:类型 = 默认值
var num: Int = 10
空安全的声明方式
var str: String ?= null
可变变量:
var: var是一个可变变量,这是一个可以通过重新分配来更改为另一个值的变量,这种声明变量的方式和Java中声明变量的方式一样。
特点:
可以重写set和get方法,注意在set和get方法中,变量用field表示,不能用变量本身,否则会出现循环调用。
var num2 = 10
    set(value) {
        field = value + 5
    }
    get() {
        return field + 5
    }
只读变量:
val: val是一个只读变量,这种声明变量的方式相当于java中的final变量,一个val创建的时候必须初始化,因为以后不能被改变。
特点:
只能重写get方法,不能重写set方法
val num3 = 10
    get() {
        return field + 5
    }
静态变量:
Kotlin中声明静态变量的方法是将对象放在对象中声明。
/**
*  静态类
*/
object StaticData {
    /**
     * 静态变量(private)
     */
    var num6 = 10
}
如果把变量放到一个普通对象中,声明出来的变量是私有的,外部调用不到,推荐使用伴生对象来声明静态变量。
/**
* 伴生对象
*/
companion object {
    /**
     * 静态变量
     */
    var num4 = 10
}
伴生对象相当于是外部类的对象,我们可以使用类直接调用,在伴生对象中声明的变量,实际上编译成java代码后对象都声明在了伴生对象的外部类里面了,伴生对象里面只是生成的set和get方法。
常量:
常量值是在编译时期就确定下来的,因此常量值可以直接赋值,也可以赋值为其他常量值,但不能赋值为非常量值,即不可以用没有被const修饰的变量给它赋值,
const只能修饰val,不能修饰var。
const val 常量值的名字 = 常量值
声明一个常量可以在伴生对象中声明
object StaticData {
    /**
     * 常量:const val
     */
    const val num7 = 10
}
也可以在类外面声明,用这种方法声明的常量,相当与创建了一个 类名称 + Kt(KotlinTestActivitykt)的对象,常量属于这个对象
const val num8 = 10
open class KotlinTestActivity : BaseActivity(), View.OnClickListener
二、Kotlin中的方法
Kotlin中声明方法的格式:
fun  [方法名] ( [参数名] : [参数类型] ) : [返回类型]{
    ...
    return [返回值]
}
有返回值:
fun add(num1: Int, num2: Int): Int {
    return num1 + num2
}
无返回值,Unit代表为空,可以省略:
fun log(msg: String): Unit {
    print(msg)
}
fun log(msg: String) {
    print(msg)
}
静态方法:需要声明在对象中object
/**
* 伴生对象
*/
companion object {
    /**
     * 静态方法
     */
    fun getData(): Int {
        return 1
    }
    /**
     * 静态方法
     */
    @JvmStatic
    fun getNewData(): Int {
        return 2
    }
}
在Java中调用需要加companion
KotlinTestActivity.Companion.getNum4()
可以通过注解@JvmStatic省略Companion直接调用
KotlinTestActivity.getNewData()
Kotlin中的方法重载:
Java中不同参数,类型的方法重写需要写多个方法,在Kotlin中只需要声明一个方法就可以解决。
/**
* @param arg1 必传
* @param arg2 必传
* @param arg3 必传
* @param arg4 可不传,不传时默认值 2
* @param arg5 可不传,不传时默认值 test
*/
fun test(arg1: String?, arg2: Int?, arg3: String?, arg4: Int? = 2, arg5: String? = "test") {
}
方法中的参数可以设置默认值,在外部没有传值得情况就可以用默认值来代替,上面那个方法中后两个参数有默认值,所有在调用这个方法的时候是可以不传那两个参数的,如果参数没有默认值,是必传的。
test("arg1", 2, "arg3", 4, "arg5")
test("arg1", 2, "arg3")
Kotlin方法中的参数也可以不一设定的顺序传递,需要指定传递的哪个参数
test(arg1 = "arg1", arg2 = 2, arg3 = "arg3")
test(arg3 = "arg3", arg1 = "arg1", arg2 = 2)
三、Kotlin中的null安全
Kotlin将变量分为可以为Nullable类型和Non-Null类型,变量默认Non-Null类型,如果想声明Nullable的变量,需要用“?”修饰,
加?表示该变量可能为空,不加则表示一定不会指向一个空引用。
声明Nullable类型变量
var name: String? = null
声明Non-Null类型变量
var name1: String = ""
name1可以直接赋值给name
name = name1
但是name要赋值给name1,必须加!!
name1 = name!!
如果name为空,就会抛出KotlinNullPointerException异常,所以Nullable类型变量要赋值给Non-Null类型变量时,要先判断是否为空,不为空才可以赋值,并且不建议使用!!。
四、Kotlin中的 data class
在 Kotlin 中,不需要自己动手去写一个 JavaBean,可以直接使用 DataClass,使用 DataClass 编译器会默默地帮我们生成以下方法
set()
get()
equals()
hashCode()
toString()
componentN()
copy()
定义一个 data class 类:
data class UserData(
        var name: String,
        var age: Int = 20,
        var avatar: String? = null,
        var userInfo: UserInfo? = null
)
虽然data class为我们生成了很多方法,减少了我们的很多代码量,但是data class 存在两个问题,没有无参数的构造方法,而且是被final修饰不能被继承,不过可以利用官方给出的插件来解决这些问题(noarg、allopen)。
buildscript {
    dependencies {
        classpath "org.jetbrains.[kotlin:kotlin-noarg:$kotlin_version](http://kotlinkotlin-noarg%24kotlin_version/)"
        classpath "org.jetbrains.[kotlin:kotlin-allopen:$kotlin_version](http://kotlinkotlin-allopen%24kotlin_version/)"
    }
}
通过插件可以帮我们去掉class的final关键字,并且生成一个无参的构造方法,但是由于是在编译器做的操作,所以在源代码中还是无法直接使用无参的构造函数,只能通过反射来使用。
@KotlinData
data class OneData(var arg: String)
class NewData(var arg2: String, var arg3: Int, arg: String): OneData(arg)
如果需要无参的构造方法,可以给每个变量都设置初始默认,或者采用一般的class
data class UserInfo(
        var info1: String? = null,
        var info2: String? = null,
        var info3: Int = 0
)
一般的class
class OtherInfo {
    var info1: String? = null
    var info2: String? = null
    var info3: Int = 0
}
五、Kotlin中扩展函数
扩展函数实际上就是一个对应Java中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的,它并没有改变类本身。
扩展函数的使用:
只需要把扩展的类或者接口名称,放到即将要添加的函数名前面。这个类或者名称就叫做接收者类型,类的名称与函数之间用"."调用连接。this指代的就是接收者对象,它可以访问扩展的这个类可访问的方法和属性。
fun test(str: String): String {
    return "back$str"
}
fun TextView.setColor(colorRes: Int) {
    this.setTextColor(context.getColor(colorRes))
}
fun ImageView.loadImage(drawableRes: Int) {
    Glide.with(this)
            .load(drawableRes)
            .into(this)
}
在外面调用:
var test = getBack("test")
tv_age?.setColor(R.color.color_4)
tv_avatar?.loadImage(R.drawable.head_bg_img)
Kotlin扩展函数允许我们在不改变已有类的情况下,为类添加新的函数,在java要调用扩展函数要将被扩展的对象传进去。
KotlinExtensionKt.getBack("test");
KotlinExtensionKt.setColor(tv, R.color.color_0);
KotlinExtensionKt.loadImage(iv, R.drawable.head_bg_img);
六、Kotlin中的Lambda表达式和高阶函数
1、Lambda表达式的本质其实是匿名函数,因为在其底层实现中还是通过匿名函数来实现的。
2、将函数作为另一个函数的参数或者返回值的函数是高阶函数
语法:
1\. 无参数的情况 :
    val/var 变量名 = { 操作的代码 }
2\. 有参数的情况     
    val/var 变量名 : (参数的类型,参数类型,...) -> 返回值类型 = {参数1,参数2,... -> 操作参数的代码 }
    可等价于
    // 此种写法:即表达式的返回值类型会根据操作的代码自推导出来。
    val/var 变量名 = { 参数1 : 类型,参数2 : 类型, ... -> 操作参数的代码 }
3\. lambda表达式作为函数中的参数的时候,这里举一个例子:
    fun test(a : Int, 参数名 : (参数1 : 类型,参数2 : 类型, ... ) -> 表达式返回类型){  
       ...  
   }
特点:
1、Lambda表达式总是被大括号括着
2、其参数(如果存在)在 -> 之前声明(参数类型可以省略)
3、函数体(如果存在)在 -> 后面。
举例:
1、无参数的情况
 // 源代码
 fun test() { 
    println("无参数") 
 }
 // lambda代码
 val test = { 
    println("无参数") 
 }
 // 调用
 test()  => 结果为:无参数
2、有参数的情况
 // 源代码
 fun test(a : Int , b : Int) : Int{
     return a + b
 }
 // lambda
 val test : (Int , Int) -> Int = {a , b ->
     a + b
 }
 // 或者
 val test = {a : Int , b : Int ->
     a + b
 }
 // 调用
 test(3,5) => 结果为:8
3、Lambda表达式作为函数中的参数的时候
// 源代码 
fun test(a : Int , b : Int) : Int{
     return a + b 
}
fun sum(num1 : Int , num2 : Int) : Int{
     return num1 + num2 
} 
// 调用 
test(10,sum(3,5)) 
// 结果为:18
// lambda 
fun test(a : Int , b : (num1 : Int , num2 : Int) -> Int) : Int{
     return a + b.invoke(3,5)
 } 
// 调用 
test(10,{ num1: Int, num2: Int -> 
    num1 + num2     
}) 
// 结果为:18
七、Kotlin的使用
1、创建一个Activity默认是私有的,如果这个activity可以被继承需要加open修饰
2、Java中的继承 extends 和实现 implements 在Kotlin中都可以用 :替换,多个implements之间添加 ,
open class KotlinTestActivity : BaseActivity(), View.OnClickListener
3、在布局中的View可以在activity直接调用,Kotlin默认给实现findViewById
<TextView
        android:id="@+id/tv_age"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:layout_margin="20dp"/>
tv_age?.setColor(R.color.color_4)
查看Kotlin转换成java后的代码
public View _$_findCachedViewById(int var1) {
   if (this._$_findViewCache == null) {
      this._$_findViewCache = new HashMap();
   }
   View var2 = (View)this._$_findViewCache.get(var1);
   if (var2 == null) {
      var2 = this.findViewById(var1);
      this._$_findViewCache.put(var1, var2);
   }
   return var2;
}
var10000 = (TextView)this._$_findCachedViewById(id.tv_age);
if (var10000 != null) {
   KotlinExtensionKt.setColor(var10000, 500082);
}
4、Kotlin中的多个数据可以拼接成一个String用{} 包起来
var num: Int = 10
var user1 = UserData("name1”)
println("num = $num")
println("num = ${user1.age}")
5、设置监听事件
系统的OnClickListener, OnTouchListener等属于SAM 构造可以使用Lambda替换,具体分析:
https://blog.csdn.net/blovecat/article/details/103767059
tv_avatar?.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
    }
})
// 当lambda表达式是函数调用的最后一个实参,它可以放到括号的外边。
tv_avatar?.setOnClickListener() {
}
// 当lambda表达式是函数唯一实参时,还可以去掉代码中的空括号对
tv_avatar?.setOnClickListener {
}
// 当lambda表达式只有一个参数,那么在调用该lambda表达式时,可以不指定它的参数名字,在lambda函数体内用it来代表这个参数.
tv_avatar?.setOnClickListener {
    it.alpha = 1f
}
// 当lambda表达式有多个参数,那么在调用该lambda表达式时,必须指定每一个参数的名字,如果某个参数用不到可以用 _ 来代替
tv_avatar?.setOnTouchListener { _, event ->
    if (event.action == MotionEvent.ACTION_DOWN) {
    }
    false
}
5、自定义View 三个构造方法可以写成一个
Java自定义View
public class TestView extends View {
    public TestView(Context context) {
        this(context,null);
    }
    public TestView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
6、Kotlin自定义View
class TestView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : View(context, attrs, defStyleAttr) {
    // 定义接口回调方式
    var testListener: TestListener? = null
    // Lambda 表达式回调
    var callBack = { s: String, i: Int -> Unit}
    // Lambda 表达式回调简化
    var callBack2: ((String) -> Unit)? = null
    var callBack3: ((String, Int) -> Unit)? = null
    /**
     * 系统的初始化方法
     */
    init {
    }
    fun backData() {
        callBack.invoke("back", 0)
    }
    fun backData2() {
        callBack2?.invoke("back")
        callBack3?.invoke("back", 1)
    }
    fun setCallBack(listener: TestListener?) {
        this.testListener = listener
    }
}
Kotlin接口定义:
interface TestListener {
    fun callBack1()
    /**
     * 在java中,接口中定义的方法不可以实现,实现类必须实现所有方法
     * 在Kotlin中,接口中的方法可提前进行空实现,实现类可不实现其不用的方法
     */
    fun callBack2() {}
}
Activit中调用回调函数:
var view = TestView(this)
view.setCallBack(object: TestListener{
    override fun callBack1() {
    }
})
view.callBack = { _: String, _: Int ->
}
view.callBack2 = {
}
7、单例模式
/**
* @Author: zs
* @Date: 20/12/23 上午8:29
* @Description:
*/
class InstanceKotlin {
    companion object{
        @Volatile
        private var mUtil: InstanceKotlin? = null
        /**
         * 两次判空实现单例
         * @return
         */
        val instance1: InstanceKotlin?
            get() {
                if (mUtil == null) {
                    synchronized(InstanceKotlin::class.java) {
                        if (mUtil == null) {
                            mUtil = InstanceKotlin()
                        }
                    }
                }
                return mUtil
            }
        /**
         * 静态内部类实现单例
         * @return
         */
        val instance2: InstanceKotlin
            get() = TestHolder.instance
        private object TestHolder {
            val instance: InstanceKotlin = InstanceKotlin()
        }
    }
}