Kotlin-通过Java反编译撕开Kotlin的高端语法糖的真面目(二)

前言

书接上回,上回跟大家聊了下kotlin的一些常用的语法和如何通过将kotlin转换为java代码来深入了解koltin语法的本质,今天我们继续了解一下kotlin的其他一些特性。

其实我在写上一篇文章的时候,介绍的一些方法主要是为了跟大家分享一下,如何去理解kotlin语法糖背后的本质,不过后来想想前面的内容又太单一简单了,因此就继续水一篇文章。我们继续了解一下一下kotlin其他的语法糖。

密封类和枚举类

枚举类我们就不详细展开了,相信大家都很了解。它常常用于定义有限的集合。因此常被我们当做单例和多例模式。

kotlin 复制代码
enum class Gender{
    MALE,// 定义男性
    FEMALE// 定义女性
}

// 使用方式,判断是否是男性
fun isMale(people : Gender) = people == MALE

那么什么是密封类呢?刚接触密封类的时候,我还是比较奇怪的,首先我们来看下密封类的使用方式

kotlin 复制代码
// 首先定义一个密封类,人类
sealed class People {
    class Male : People() // 定义男性
    class Female : People() // 定义女性
}

// 使用方式,判断是否是男性
fun isMale(people : People) = people is Male

在形式上看似比较像,但是这里有一个很大的区别,枚举类里面我们使用的是 == 进行判断,密封类我们是使用is(类似Java的instanceOf),看到这里大家可能已经有一个大致的区分。枚举类定义出来的,已经是类的实例对象,而密封类定义的是类的子类。他们都用于表示限定的类层次结构,但是方式不同。因此,在使用上,我们常常拿枚举当做单例或者多例模式。而密封类常常当做一个被限定子类的集合,但是子类的实例可以有多个,就好比如我们去请求一个接口,返回的结果成功、失败、异常,但是可以有很多次成功、失败、异常。 再回到上面的例子看,枚举里的男性、女性我们通常可以设定为一个属性,而密封类的男性、女性我们设定为一个子类特征集合,他们互不冲突,两者是可以互补的,我们讲上述的例子结合一下。

kotlin 复制代码
// 首先定义一个密封类,
enum class Gender{
    MALE,// 定义男性
    FEMALE// 定义女性
}

sealed class People(gender : Gender) {
    class Male : People(Gender.MALE) // 定义男性
    class Female : People(Gender.FEMALE) // 定义女性
}

我们将密封类和枚举类结合,枚举类的Gender是密封类People的一个性别属性。看到这里,大家基本上就可以分清楚枚举类和密封类的区别了。

我们将上述代码转换成Java代码看一下:

scala 复制代码
// Gender.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;
// 定义的枚举类型
public enum Gender {
   MALE,
   FEMALE;
}
// People.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;

// 定义的密封类,我们发现
// 其实我们定义的密封类在Java当中是一个抽象类
public abstract class People {
   private People(Gender gender) {
   }

   // $FF: synthetic method
   public People(Gender gender, DefaultConstructorMarker $constructor_marker) {
      this(gender);
   }
   
   // 我们定义的Male子类是密封类的静态内部子类
   public static final class Male extends People {
      public Male() {
         super(Gender.MALE, (DefaultConstructorMarker)null);
      }
   }
   // 我们定义的Female子类是密封类的静态内部子类
   public static final class Female extends People {
      public Female() {
         super(Gender.FEMALE, (DefaultConstructorMarker)null);
      }
   }
}

看到这里,大家应该就能理解枚举类和密封类的本质区别在哪里了。 我们知道,在kotlin中,枚举类和密封类都是支持when(类似Java的switch-case语句)的判定的。我们来看下以下代码:

kotlin 复制代码
// 通过枚举类型的判定
fun isMaleByGender(gender: Gender) : Boolean =
    when(gender){
        Gender.MALE-> true
        Gender.FEMALE -> false
    }

// 通过密封类的判定
fun isMaleByPeople(people: People) : Boolean=
    when(people){
        is People.Male -> true
        is People.Female -> false
    }

枚举类型的判定是直接通过when(param) -> value 的方式判定的,而密封类的是通过when(param) -> is value的方式进行的。

kotlin的运算符重载

kotlin和C++一样,是支持运算符重载的,可是Java是没有运算符重载的。那么为什么Java没有运算符重载呢?因为Java的设计者认为,运算符本质上也是函数的调用。没错,其实kotlin的运算符本质上也是方法调用,只不过kotlin的编译器帮助我们能够使用重载之后的运算符进行开发,提高我们的开发效率。

下面,我们来看一个运算符重载的例子

kotlin 复制代码
// 一个简单的数据类
data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Foo) : Foo = Foo(x + other.x, y + other.y)
}

fun main(args: Array<String>) {
    // 使用的时候
    val f1 = Foo(10, 20)
    val f2 = Foo(30, 40)
    // 直接用+运算符代替plus函数,事实上会调用plus函数
    println(f1 + f2) // 打印内容为Foo(x=40, y=60)
}

在上述例子当中,我们实现了加法运算符号的重载来计算两个坐标相加的功能。其实例子很简单,我们来简单了解下运算符重载的本质是什么。老规矩,我们看下翻译成Java代码

java 复制代码
// Point.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class Point {
   private final int x;
   private final int y;

   @NotNull
   public final Point plus(@NotNull Point other) {
      Intrinsics.checkNotNullParameter(other, "other");
      return new Point(this.x + other.x, this.y + other.y);
   }

   public final int getX() {
      return this.x;
   }

   public final int getY() {
      return this.y;
   }

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
   ...
}
// KtOperatorKt.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

public final class KtOperatorKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, "args");
      Point f1 = new Point(10, 20);
      Point f2 = new Point(30, 40);
      Point var3 = f1.plus(f2);
      System.out.println(var3);
   }
}

我们发现,在Java当中,我们的"+"的操作符,被转换成了plus方法,这是kotlin帮我们做的。所以,本质上,kotlin的运算符重载就是方法调用,其实我们使用的block() 亦是如此,他重载了invoke。更多支持重载的运算符,这里就不一一展开了,可以查看kotlin的中文网的说明。

kotlin的解构

在kotlin当中,我们可以将对象的属性赋值给多个新定义的属性,这种被称为kotlin的解构。我们看下下面这段代码

kotlin 复制代码
data class Human(
    val name : String,
    val age : Int,
    val gender : Int
)

fun deconstruct(){
    val human = Human(
        name = "Jack",
        age = 18,
        gender = 1
    )
    // 直接定义解构接收的多个参数
    // 接收的参数类型需要对齐
    val (name, age , gender ) = human
    println(
        "name:$name,age:$age,gender:$gender"
    )
}

这里是不是觉得好理解,其实本质上应该就是参数的赋值吧。那么究竟是不是呢,我们来看一下Java对应的代码

kotlin 复制代码
// Deconstruct.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;

@Metadata(
   mv = {1, 8, 0},
   k = 1,
   d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
   d2 = {"Lcom/yuanyi/myapplication/kt/Deconstruct;", "", "()V", "app_debug"}
)
public final class Deconstruct {
}
// Human.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 8, 0},
   k = 1,
   d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\f\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B\u001d\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005\u0012\u0006\u0010\u0006\u001a\u00020\u0005¢\u0006\u0002\u0010\u0007J\t\u0010\r\u001a\u00020\u0003HÆ\u0003J\t\u0010\u000e\u001a\u00020\u0005HÆ\u0003J\t\u0010\u000f\u001a\u00020\u0005HÆ\u0003J'\u0010\u0010\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u00052\b\b\u0002\u0010\u0006\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u0011\u001a\u00020\u00122\b\u0010\u0013\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0014\u001a\u00020\u0005HÖ\u0001J\t\u0010\u0015\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\b\u0010\tR\u0011\u0010\u0006\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\n\u0010\tR\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u000b\u0010\f¨\u0006\u0016"},
   d2 = {"Lcom/yuanyi/myapplication/kt/Human;", "", "name", "", "age", "", "gender", "(Ljava/lang/String;II)V", "getAge", "()I", "getGender", "getName", "()Ljava/lang/String;", "component1", "component2", "component3", "copy", "equals", "", "other", "hashCode", "toString", "app_debug"}
)
public final class Human {
   @NotNull
   private final String name;
   private final int age;
   private final int gender;

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

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

   public final int getGender() {
      return this.gender;
   }

   public Human(@NotNull String name, int age, int gender) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
      this.gender = gender;
   }
   
   // 我们发现,多了component1~3的方法
   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }

   public final int component3() {
      return this.gender;
   }
   // 这里还多了一个copy的方法
   @NotNull
   public final Human copy(@NotNull String name, int age, int gender) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Human(name, age, gender);
   }

   // $FF: synthetic method
   public static Human copy$default(Human var0, String var1, int var2, int var3, int var4, Object var5) {
      if ((var4 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var4 & 2) != 0) {
         var2 = var0.age;
      }

      if ((var4 & 4) != 0) {
         var3 = var0.gender;
      }

      return var0.copy(var1, var2, var3);
   }

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

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

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

         return false;
      } else {
         return true;
      }
   }
}
// DeconstructKt.java
package com.yuanyi.myapplication.kt;

import kotlin.Metadata;

@Metadata(
   mv = {1, 8, 0},
   k = 2,
   d1 = {"\u0000\b\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001¨\u0006\u0002"},
   d2 = {"deconstruct", "", "app_debug"}
)
public final class DeconstructKt {
   public static final void deconstruct() {
      Human human = new Human("Jack", 18, 1);
      // 关键在这里,我们发现,参数的或并不是通过class#getParams()的方式,而是通过class#componentN()的方式赋值的
      String name = human.component1(); 
      int age = human.component2();
      int gender = human.component3();
      String var4 = "name:" + name + ",age:" + age + ",gender:" + gender;
      System.out.println(var4);
   }
}

那么为什么不是使用getParams的方式,而是要多几个方法呢?这不是多此一举吗?其实不然,不知道大家发现没有,上述的class我使用的是data class,在kotlin当中,data class都会默认生成componentN和copy的方法,原因就是这个,当我们去掉data的,让其变成一个普通的class的时候,我们发现会报如下错误。

我们按照提示,将componentN的方法加上去如下:

我们发现,代码顺利通过。这里我们惊奇的发现,componentN前面有个关键字,是operator,没错,解构的本质就是kotlin的操作符重载,之所以把解构放在操作符重载后面讲就是这个原因。

解构的方式其实我们还可以用在lamda表达式当中,如下所示:

kotlin 复制代码
fun deconstruct(){
    val human = Human(
        name = "Jack",
        age = 18,
        gender = 1
    )
 
    blockDeconstruct(human){
            (name,age,gender)-> // 前提是支持解构
                run {
                    println(
                        "name:$name,age:$age,gender:$gender"
                    )
                }
    }
}

fun blockDeconstruct(human: Human,block : (Human) -> Unit) = block(human)

其实kotlin帮我们封装了很多的方法,来提升我们的开发效率,比如Collections当中的filter、map都是依靠支持迭代器(Iterator)的扩展函数实现的,因此我们在学习kotlin的语法糖的时候,要找对方法。不过也不是所有的语法糖都能通过java代码可以看出来的,有些很多也是依赖编译器一起实现的,例如协程的原因。那么本次介绍的语法糖就到这里吧。

相关推荐
一点媛艺7 分钟前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风11 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k3 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小104 小时前
JavaWeb项目-----博客系统
android
风和先行5 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.5 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰6 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶6 小时前
Android——网络请求
android
干一行,爱一行6 小时前
android camera data -> surface 显示
android
断墨先生7 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app