Kotlin入坑之路--从排斥到拥抱(下篇)

有个同事看了我的上篇文章,说他深受我的启发,不想去卖手抓饼,还是要与时俱进,所以现在开始努力学习kotlin了。既然我的文章影响了一位年轻的小伙子奋发图强,那我要继续影响更多的小伙子,鼓励他们去学习kotlin, 避免他们以后都去卖手抓饼,和我抢生意。。。

本文学习要点以下:

  • kotlin类、对象和接口
  • kotlin 可空性
  • kotlin lambda
  • kotlin 泛型、kotlin注解和反射

也许你看了我的上篇文章, 觉得kotlin好像也没比java好多少,觉得还是用回java比较好。小伙子,你这种思想觉悟还是不行啊,kotlin很多重要特性我还没有介绍呢,你这种思想很适合和我一起开三轮车去卖手抓饼。。。如果你还没看我的上一篇文章Kotlin入坑之路–从排斥到拥抱(上篇)
,你可以先去瞄瞄, 然后再来看这篇。kotlin特性的重要要点,且听我以下一一述说:

一、kotlin类、对象和接口

1.1 接口

kotlin接口声明和java一样,使用interface关键字,kotlin接口可以有默认实现的方法, 默认的属性(默认的属性不能直接赋予初始值)。

  • kotlin里面使用冒号:代替java里面的extendsimplements
  • kotlin也是最多只能继承一个类,但可以实现任意多的接口
    举个栗子:
interface OnClickListener {
    val buttonType: Int = 100 //这行会编译不通过,因为赋予初始值
    val buttonName : String   //这样声明就ok, 原理等会介绍
    fun onClick()
    fun onShow() {
        print("onShow HH")
    }
}
class Button : OnClickListener{
    override val buttonName: String
        get() = "Hbx"

    override fun onClick() {
        print("onClick hh")
    }
}
fun main(args: Array<String>) {
    val hhButton =  Button()
    hhButton.onShow()  // 打印-> onShow HH
    hhButton.onClick()  // 打印-> onClick hh
    print(hhButton.buttonName) // 打印 -> Hbx
}

kotlin是兼容java 6的,但只有java 8及以上版本接口里面才允许带默认方法,那kotlin为什么可以带默认方法呢?我们反编译上面的kotlin代码为java代码,原理一看便知,反编译后的java代码如下,详细请看代码的注释:

public interface OnClickListener {
   // kotlin编译器生成了getButtonName方法,等待子类实现该接口
   String getButtonName();
   void onClick();
   // kotlin编译器生成了onShow方法
   void onShow();
   // kotlin编译器生成了一个静态类
   public static final class DefaultImpls {
      public static void onShow(OnClickListener $this) {
         String var1 = "onShow HH";
         System.out.print(var1);
      }
   }
}

public final class Button implements OnClickListener {
   public String getButtonName() {
      return "Hbx";
   }

   public void onClick() {
      String var1 = "onClick hh";
      System.out.print(var1);
   }

   //kotlin默认帮我们实现了onShow方法,并且调用了静态类的方法。这就是kotlin接口为甚么可以有默认方法的原因。
   public void onShow() {
      OnClickListener.DefaultImpls.onShow(this);
   }
}

public static final void main(@NotNull String[] args) {
      Button hhButton = new Button();
      hhButton.onShow();
      hhButton.onClick();
      String var2 = hhButton.getButtonName();
      System.out.print(var2);
   }

1.2 open、override、final和abstract

  • 和java不同,kotlin的类默认是不可以继承的,方法默认是不可重写的。如果需要继承或重写,需要为类或方法添加open声明
  • 使用override关键字(不是注解) 声明该方法是重写父类方法的,使用final关键字可以终止子类重写方法
open class Button : OnClickListener{ //open关键字说明Button可以被作为父类被继承
    override val buttonName: String   // 子类可以重新该方法
        get() = "Hbx"

    final override fun onClick() {  //final表明子类继承Button时,不可重写onClick方法了
        print("onClick hh")
    }

    fun test() {               //没有使用open声明方法,子类不可重写该方法
        print("test")
    }
}
  • 使用abstract声明抽象类,可以带有抽象方法和默认方法,抽象类不可以被实例化为对象。
abstract class BasePerson { //abstract类就是open的,不需要open声明
     abstract fun getName() // 子类需重写
     open fun show() {      // 子类可重写
        print("show")
     }

     fun test(){            // 子类不可重写,因为非open
     }
}

1.3 内部类和嵌套类

  • 当在kotlin类中用class声明一个类时,它是静态内部类(这点和java不同,如果只用class声明,在java里面属于内部类),静态内部类不可以访问外部类成员,但内部类是可以的。
  • 要想声明一个内部类,请使用inner class关键字
class Button {
    inner class InnerClass {
        //我是内部类
    }

    class StaticInnerClass {
        //我是静态内部类
    }
}

1.4 类的构造方法和初始化

kotlin的构造方法可以和类声明直接结合起来。构造函数constructor声明,初始化语句用init声明,一个类中可以有多个初始化语句。下面的类中声明了2个构造函数,2个初始化块。当执行完构造函数,就会从上到下执行初始化语句。

class Person(val name: String, var age : Int) {

    constructor(name: String) : this (name, 17){
        //构造函数
    }

    init {
        // 初始化语句1,在这里可以初始化变量啊,执行方法啊等等
    }

    init {
        // 初始化语句2
    }
}

1.6 数据类

在Java中我们声明一些model POJO类时,要写一堆set/get方法,然后重新hashcode()、equals()、toString()等方法,甚是麻烦。为了简化这些操作,kotlin提供data关键字声明类,然后由编译器帮我们生成以上方法,所以说kotlin代码真的可以很简洁。话不多说,上码:

//声明了一个kotlin类
data class Student(var name : String, var age : String)

反编译后的Java代码大概如下:

public final class Student {
    private String name;
    private String age;
    public final String getName() {
        return this.name;
    }
    public final void setName(@NotNull String var1) {
        this.name = var1;
    }

    public final String getAge() {
        return this.age;
    }

    public final void setAge(@NotNull String var1) {
        this.age = var1;
    }

    public Student(@NotNull String name, @NotNull String age) {
        this.name = name;
        this.age = age;
    }

    @NotNull
    public final String component1() {
        return this.name;
    }

    @NotNull
    public final String component2() {
        return this.age;
    }

    @NotNull
    public final Student copy(@NotNull String name, @NotNull String age) {
        Intrinsics.checkParameterIsNotNull(name, "name");
        Intrinsics.checkParameterIsNotNull(age, "age");
        return new Student(name, age);
    }

    // $FF: synthetic method
    public static Student copy$default(Student var0, String var1, String var2, int var3, Object var4) {
        // 省略代码
        return var0.copy(var1, var2);
    }

    @NotNull
    public String toString() {
        return "Student(name=" + this.name + ", age=" + this.age + ")";
    }

    public int hashCode() {
        String var10000 = this.name;
        int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
        String var10001 = this.age;
        return var1 + (var10001 != null ? var10001.hashCode() : 0);
    }

    public boolean equals(@Nullable Object var1) {
        if (this != var1) {
            if (var1 instanceof Student) {
                Student var2 = (Student)var1;
                if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.age, var2.age)) {
                    return true;
                }
            }

            return false;
        } else {
            return true;
        }
    }
}

1.7 object关键字

object关键字作用有三种:

  • 对象声明
    在kotlin中,声明一个单例类易如反掌, 以下代码就声明了一个单例类:
object FileManager{
    fun getTargetFile() : String {
        return "/sdcard/"
    }
}

fun main(args: Array<String>) {
    FileManager.getTargetFile() //调用单例对象的方法
}

反编译后的Java代码如下:

public final class FileManager {
    public static final FileManager INSTANCE;

    public final String getTargetFile() {
        return "/sdcard/";
    }

    private FileManager() {
    }

    static {
        FileManager var0 = new FileManager();
        INSTANCE = var0;
    }
}

public static final void main(@NotNull String[] args) {
    Intrinsics.checkParameterIsNotNull(args, "args");
    FileManager.INSTANCE.getTargetFile();
}

也就是说,使用object声明的对象,它是饿汉式的,这点需要留意呀。 同时,使用object声明的对象,不可以声明构造函数,它的构造函数是private的,所以如果你需要在构造对象时想同时传递参数,那么object声明的单例方式就不适用了。当然,使用object声明的对象也可以继承类和实现接口。例子:

interface OnSearch
open class SuperManager{
    fun addFile() {

    }
}
object FileManager : SuperManager(), OnSearch{
    fun getTargetFile() : String {
        addFile()
        return "/sdcard/"
    }
}
  • 伴生对象
    kotlin里面没有static关键字声明静态方法和静态属性。采用companion object声明一个伴生对象,可以代替static
class Student(val name : String){

    companion object {
        private const val schoolName = "GDUT"
        fun getSchool() : String {
            return schoolName
        }
    }

}
fun main(args: Array<String>) {
    //访问Student静态方法getSchool()
    val schoolName1 = Student.getSchool()
}

其实实现原理是很简单的,感兴趣的童鞋可以自己编译代码看一下。算了,还是我贴出来一下, 反编译后的代码大概如下:

public final class TestKt {
    public static final void main(@NotNull String[] args) {
        Intrinsics.checkParameterIsNotNull(args, "args");
        String schoolName1 = Student.Companion.getSchool();
    }
}

public final class Student {
    @NotNull
    private final String name;
    private static final String schoolName = "GDUT";
    public static final Student.Companion Companion = new Student.Companion((DefaultConstructorMarker)null);

    public final String getName() {
        return this.name;
    }

    public Student(@NotNull String name) {
        this.name = name;
    }

    public static final class Companion {
        @NotNull
        public final String getSchool() {
            return "GDUT";
        }

        private Companion() {
        }

        // $FF: synthetic method
        public Companion(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
}
  • 可以代替Java的匿名内部类
    kotlin声明匿名对象的方式,例子:
 TextView(this).setOnClickListener(object  : View.OnClickListener {
        override fun onClick(v: View?) {
            //onClick
        }
    })

二、kotlin可空类型和不可空类型

在Java里面,很多程序崩溃的原因就是NullPointerException, 为了在编译期间就检测出不安全的调用,kotlin提供了可空类型的支持。这时候?大有作用。

  • 可空类型:声明变量时,需要在类型后面加个?
  • 不可空类型:声明变量时,在类型后面没有?
fun main(args: Array<String>) {

    //可空类型:声明变量在类型后面加个?
   val name : String? = "hh" //在声明变量时,在类型后面加个?,代表这个变量可以赋予null值
    val nameLength = name.length  //这行代码编译不通过的,因为 kotlin认为它可能存在null的值。
    //如果想正常获取name长度,你需要对name判空处理
    if (name!=null) {
        val nameLength = name.length // 这行代码可以编译通过,因为你已经做了判空处理
    }


    //不可空类型:在类型后面没有?
    var school : String = "GDUT"
    school = null  // 这行代码编译不通过的,因为school被声明为不可空类型,所以不能赋予null值
}

通过以上方式,就可以在编译期间,就可以发现空对象调用问题,避免了运行时出错

1 安全调用符: ?.

当使用可空类型时,我们都要对该变量对象进行一次非空判断才可以调用该变量对象的方法,甚是麻烦。kotlin为我们提供了 ?.操作符,简化了这个操作。

fun main(args: Array<String>) {

   val name : String? = "hh"

    name?.toUpperCase()  //这行代码编译通过的
    /*name?.toUpperCase() 相当于以下调用
   if (name != null) {
       name.toUpperCase
   } else {
       null
   }*/

}

2 Elvis运算符: ?:

对于上述?.运算符,当对象为null时,调用方法,总是返回null。但我们希望对象为空时,通过?.运算符调用方法,不返回null, 而是需要返回一个默认值,需要怎样做呢? kotlin提供了?:运算符

fun main(args: Array<String>) {
   val name : String? = "hh"
    //当对象为空时,会返回0, 否则返回name.length
    val length = name?.length ?: 0
}

3 安全转换: as?

当我们强转一下类型时,但类型不匹配时,直接使用as会抛出ClassCastException; 但使用as?,不会抛出ClassCastException, 而是返回null值

class Student
class Person

fun main(args: Array<String>) {
   val student = Student()
    val studentTemp = student as Person //这里运行时会抛出ClassCastException, 因为类型不匹配
    val studentTemp1 = student as? Person //这里不会抛出ClassCastException, 而是返回null值
}

4 非空断言: !!

使用!!,就是告诉编译器,我们非常确定这是非空的对象调用,请编译通过。但是如果在空对象使用该符号,将会在运行时出错。

fun main(args: Array<String>) {
   var name :String?= "Hbx"
    //使用!!,告诉编译器,我们确定是非空的对象调用
    name!!.toUpperCase()

    name = null
    //使用!!,告诉编译器,我们确定是非空的对象调用。但name对象是null的,所以运行时会崩溃
    //所以,!!仅在我们认为对象一定非空的情况下才使用,需谨慎使用
    name!!.toUpperCase()
}

三、kotlin lambda

lambada是kotlin很好用的一个特性,当你了解它后,我想你就不想再有想用回java的想法了。当你看到这个标题,你以为你终于可以先学习lambada了。但lambda内容实在太多了,我写不动了,再写我就吐血而亡了。。。但你一定要学习它,理解它的原理,当你学会它后,你可以将你的代码写得很6。《kotlin实战》你指得拥有。

四、kotlin泛型、kotlin注解和反射

这部分写得比较简单,但实际相关知识概念还是挺复杂的,在开发中相对来说也比较少用,但如果你想成为一个高级开发者,这些也是必须学会的,具体更多知识概念,你可以看《kotlin实战》这本书

1、泛型

因为kotlin泛型概念比较多和抽象,很难写得很详细,所以在这里我只是简单介绍一下泛型方法和泛型类。

  • 泛型方法
    声明泛型方法和java类似
fun <T> yourFunction( params : T) : T{
    val value = params
    print(value)
    return value
}
  • 泛型类
    使用方法也和java类似
interface ListInterface<T> {
    fun setValue(value : T)
}

class MyList<T>: ListInterface<T> {

    override fun setValue(value: T) {

    }
}

2、注解

2.1 声明注解
  • kotlin中,注解声明使用关键字annotation,其声明的注解类不允许有方法体
  • 注解只能拥有以下类型参数:基本数据类型、字符串、枚举、类引用、其他注解类,以及前面类型的数组。
@Target(AnnotationTarget.FUNCTION)
annotation class HHTest(val name: String)
2.2 使用注解

kotlin里面使用注解的语法和JAVA完全一样的。

@HHTest("test")
fun test() {
    // do nothing
}

3、反射

在kotlin中,你仍可沿用java的反射类对对象进行反射。但kotlin也提供了其专有的反射API:KClass、KCallable、KFunction和KProperty。

KClass类

相当于java的Class类,可以列举一个类和它超类的所用声明。如何获取一个KClass实例:

  • 通过MyClass::class就可以获取一个KClass实例。
  • 对于运行的对象,可使用 object.javaClass.kotlin
class Student

fun main(args: Array<String>) {
    val one = Student::class

    val student = Student()
    val two = student.javaClass.kotlin
}

KClass对象如何转换为java的Class对象呢?使用KClass的java扩展属性

fun main(args: Array<String>) {
    val one = Student::class
    val javaOne = one.java  //KClass的对象转换为Java的Class对象

通过KClass对象,你可以反射获取类中的方法、属性等等,反射方式和传统JAVA反射差不多,只不过调用的API不同而已,这里不作详细介绍了。

你可能感兴趣的:(Kotlin,android,kotlin)