从java的角度看kotlin特性


java之所以强大,是因为自身设定的时候,需要先经过一个编译过程,生成class文件。
然后class文件再经由java虚拟机(JVM),解释执行。(这里以最初的情况为基准,不考虑JIT、ART等技术)
java语法规范和JVM规范完全是两个分离的部分,这也是Kotlin等其他JVM语言成功的基础。


事实上,JVM语言还有很多,但好像只有Kotlin突然间变得很火爆,而其他一些可能功能更强 的语言,如Scala虽有其特定的场景,却一直默默无闻,这就说明Kotlin必定有它优秀的品质


还有一点,跟其他JVM语言一样,既然Kotlin还是要在JVM执行,那么必然会生成class字节码,而它一些优于Java的语法或功能,肯定是可以通过Java来实现的

一、基础功能对比

1、基本数据类型

  • java:包括基本数据类型与包装器类型(下面仅以基本类型列出)
    • 整型类:short,int,long
    • 浮点类:float,double
    • 数据流/数据:字节字符:byte,char
    • 布尔:boolean
  • kotlin:无基本数据类型,使用的对象类型与java的基本类型可以对应

因为kotlin中基本数据操作也是对象类型,因此它可以标识所有的对象:可能为空,这也是它最主要的语言特性

而抛弃Java的基本类型,则可以避免一些隐藏性空指针的发生,比如对于这段Java代码:

Integer a=1;
int b=a;

经过编译器的自动处理后,对应字节码会是这样:

这里写图片描述

如果将该字节码翻译回Java源码应该会是这样的(有些class文件查看器可能显示的和之前的java代码一样,这时候就需要直接查看字节码了):

Integer a=1;
int b=a.intValue();

此时如果变量a初始值为null,那么经过语法糖解除(去除自动拆装箱)后,就会出现NPE(NullPointerException)

而Kotlin则可以很好的避过这一点,比如像刚才那个代码使用Kotlin会是这样的几种情况(当然,因为Kotlin没有基本类型的缘故,使用Kotlin模仿这些代码是没有实际意义的):

为了更好的查看处理方式,放置编译器自动优化代码,我们采用方法的形式:

使用断言来断定对象非空
fun test(a: Int?): Unit {
    // 强行断言:如果a为null,则直接抛出异常
    val b = a!!.toInt()
}

前面也说过,Kotlin最后生成了class文件,那么可以把生成的class文件转成java源码来看如何实现:

public final void test(@Nullable Integer a) {
    if (a == null) {
        Intrinsics.throwNpe();
    }

    int b = a;
}

可以看到,转成的java代码与Kotlin表示的语义相同

Intrinsics.throwNpe()表示抛出java的NPE异常:

Intrinsics.java:

public static void throwNpe() {
    throw sanitizeStackTrace(new KotlinNullPointerException());
}

KotlinNullPointerException.java

public class KotlinNullPointerException extends NullPointerException {
    public KotlinNullPointerException() {
    }

    public KotlinNullPointerException(String message) {
        super(message);
    }
}
指定传入对象非空
fun test(a: Int): Unit {
    val b = a.toInt()
}

fun tt(){
    //指定test方法的参数非空,在方法传入null时,无法编译通过
    //test(null)
}

这里注释没有放开,否则会编译错误,这样的方式也可以保证不会出现空指针异常

设定参与对象方法调用的地方需要处理空的情况
fun test(a: Int?): Unit {
    val b = a?.toInt()
}

像这样,指定对象a可以为null,之后对象a参与方法调用的地方使用 ?.操作符
其实方法体的代码相当于这样:

val b = a?.toInt() ?: null

表示如果a不是null,那么b就是 a.toInt()的返回值,如果a为null,那么b就是null了,即:b的类型为 Int?

如果在a为null的时候,不想让b为null,则可以指定具体的值

fun test(a: Int?): Unit {
    val b = a?.toInt() ?: 2
}

这样的话,b在a为null时,值就是2,此时查看b的类型,就会发现变成了:Int,注意,这里没有问号

那么针对这个代码,翻译回java源码是什么样子呢:

public final void test(@Nullable Integer a) {
   int b = a != null ? a : 2;
}

这里是把Kotlin的Int类型转成java的int类型,那么不禁会思考,如果这里在a为null时没有使用默认的值:2,那b会是什么类型:这里我们需要改变一下kotlin代码的返回类型,否则编译器会把整段代码优化掉:

kotlin源码:

fun test(a: Int?): Int? {
    val b = a?.toInt()
    return b;
}

生成class转成java:

public final Integer test(@Nullable Integer a) {
   Integer b = a != null ? a : null;
   return b;
}

如正常猜想,确实是转成了Integer类型

2、对象基类型,以及空返回值

java对象(可实例化)的父类一定是Object,而Kotlin也指定了根类型:Any

那么Any和Object是什么 关系呢?答案是。。。

这个也通过类似上面的代码转换来查看一下:

kotlin源码:

class Test() : Any() {
    fun gett(){
        val superclass = javaClass.superclass
    }
}

生成class转成java代码:

public final class Test {
   public final void gett() {
      Class superclass = this.getClass().getSuperclass();
   }
}

可以看到Any类没了,生成的java类也没有明显的父类,而java默认给的父类是Object;
通过查阅文档也可以发现,Any类是所有kotlin类的父类,而Any类在生成字节码时会对应到java类的Object对象。

3、数组类型

java的数组类型还是很复杂的,首先他们通过虚拟机自己生成,并且还存在着继承关系,表面上来看呢,又具有一样的格式,这个可以参考另一篇博客,里面通过debug分析了java中数组的实际类型以及继承关系:Java中的数组和List集合以及类型强转

而查看Kotlin中的数组,发现头很大,里面竟然明显的区分了基本封装类的数组以及普通引用对象数组:

这里写图片描述

很头疼,针对java的八种数据类型,竟然分别实现了不同的数组,并且看注释的话,可以看到里面说:

当ByteArray用于JVM时,会作为byte[]类,其他七种类型亦是如此

然后其他引用类型对应的数组类是:Array

这里写图片描述

这就炸了,竟然引用类型的 直接父类就是Any,也就是Object,这和java的数组继承关系有很大的区别 ,然后看注释有写到:

表示一个数组(需要注意,针对JVM平台的话是一个java 数组)。

如果只看注释我们不难猜测他们的对应关系 :

  • ByteArray等八个类分别对应了java八种基本类型数组,如 byte[ ]
  • Array 对应java中的引用对象数组,像T[ ]这种格式

其实这个具体的逻辑已经被分析出来的,具体的源码可以参考:为什么不直接使用 Array 而是 IntArray ?

4、集合类型

java集合类型算是很全面很丰富,kotlin既然这么兴旺,肯定有对集合类单独处理;

与java一样,kotlin中集合类也可以分为:List,Set,Map

表示的含义也与java一样,不过很多地方还是不一样的:

  • kotlin中集合类都分为可变和非可变,并且两种类之间是继承关系 !!!,以List集合来看
    这里写图片描述
    很明显可变List(MutableList类)继承自List,且实现了MutableCollection接口,只有实现了该接口的集合,才拥有添加删除等操作元素的功能,甚至于实现了可变接口的集合类可以在迭代时进行移除操作:
public interface MutableIterator<out T> : Iterator<T> {
    /**
     * Removes from the underlying collection the last element returned by this iterator.
     */
    public fun remove(): Unit
}
  • Kotlin自定义了EmptyList类,表示空集合 ,这个类在java中是没有现有的对应类的,可以查看class转化为java源码类间关系:
public final class EmptyList implements List, Serializable, RandomAccess, KMappedMarker
  • java中List集合,最常用的是两种:ArrayList,LinkList,前者基于数组,后者基于链表,实现方式不同,则对应不同场景下的性能肯定不同,而Kotlin则很省事,可以查看Kotlin创建List的方式:

    • listOf()
    • mutableListOf()
    • arrayListOf()
    • listOfNotNull()

    最后获得的List对象,要么是EmptyList类型,要么是通过将一个数组转化为List集合,这个转化使用的是java的Arrays.asList()方法。这个方法实现是不可见的,但我们可以通过debug模式来调试查看生成对象的类型:
    这里写图片描述

    这里写图片描述

    这就比较尴尬了,生成的对象是两种类型,不过说到底,都是“Array”类型的实现没有“Link”类型的集合,这点还真不知道是出于什么考虑

  • Kotlin中Set和Map则更是只在java源码基础上进行扩展,不过是添加了一些额外的方法,方便使用,这点与其他JVM语言不同,不过也因此保证了和Java的高融合性。

5、对象定义规范

java中对象定义很 ,需要定义的属性,方法,构造函数等,都需要明确的声明,这样虽说很明朗,但即便是有快捷键的存在,要完成大量的属性方法生命依然有些让人恼火。

kotlin中则采用了很简洁的方法,例如这样:

class RR(var age: Int, var name: String)

这一行代码中同时指明了:类名,类成员域,构造函数,成员方法

public final class RR {
   @Nullable
   private Integer age;
   @NotNull
   private String name;

   @Nullable
   public final Integer getAge() {
      return this.age;
   }

   public final void setAge(@Nullable Integer var1) {
      this.age = var1;
   }

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

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public RR(@Nullable Integer age, @NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.age = age;
      this.name = name;
   }

   // $FF: synthetic method
   public RR(Integer var1, String var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = "111";
      }

      this(var1, var2);
   }
}

不符合Java语法规范的地方可以暂时忽略,合成的那个方法一般也用不到;编辑器自动生成了成员域以及get、set方法,以及构造函数。

同样的,在kotlin中还有很多新东西,比如可以针对抽象类添加抽象属性,抽象属性实现继承,但真实情况是什么呢?

abstract class KK {
    abstract var abattr: String
}

新建一个抽象类,其中有抽象属性:abattr

然后查看编译成的java源码:

public abstract class KK {
   @NotNull
   public abstract String getAbattr();

   public abstract void setAbattr(@NotNull String var1);
}

。。。事实上这个属性对应的是抽象方法,所以才要求声明抽象属性的时候,类必须定义为抽象类;那么理所当然,抽象属性被继承时,需要定义get和set的实现,这样被实现的方法中才能有自身的逻辑:

class RR(var age: Int?, var name: String) : KK() {
    override var abattr: String
        get() {
            return "abbbb"
        }
        set(value) {
            this.name = "abattr"
        }
}

对应java源码中方法部分:

   @NotNull
   public String getAbattr() {
      return "abbbb";
   }

   public void setAbattr(@NotNull String value) {
      Intrinsics.checkParameterIsNotNull(value, "value");
      this.name = "abattr";
   }

也就是说,对应的“属性”根本不具备存储的能力,新添加的属性在虚拟机中没有对应的字段。


kotlin很多特性在java的角度来看,实现有些 “粗糙” ,比如java中如果一个类的多个父类中,存在已实现的相同的方法签名,那么编译器会进行提示:

这里写图片描述

表示编译器不知道该方法在使用时到底采用那个接口中的默认实现(java8中的新特性,接口可实现default方法
如果真有这种需求,就需要对 f 方法进行重写,指明其具体的执行逻辑:

class RR implements II, IB {
    @Override
    public void f() {
        II.super.f();
        IB.super.f();
    }
}

interface II {
    default void f() {
        System.out.println("II f");
    }
}

interface IB {
    default void f() {
        System.out.println("IB f");
    }
}

这样就可以正确调用了,上面代码如果调用f方法,输出结果为:

这里写图片描述

这里需要注意的是,如果编译器能分清或者说有倾向调用两者中的一个,那么子类是不需要重写该重名方法的,比如这样:

public class TTTT {
    @Test
    public void f() {

    }
}

class RR extends TTTT implements IB {
    public void a() {
        f(); //TTTT中的方法f
        super.f();//TTTT中的方法f
        IB.super.f();//IB中的方法f
    }
}

interface IB {
    default void f() {
        System.out.println("IB f");
    }
}

直接调用f方法,编译器会倾向于调用父类(非接口)中的方法,如果需要调用接口中对应的函数,则需要通过class.super.method() 明确指定

上面主要提到java中对于重名方法的处理,在kotlin中也是有 覆盖冲突 这个概念的,这主要是说:

如果一个类从它的直接父类继承 了同一个函数的多个实现,那么必须重写这个函数并且提供自己的实现

在java中,如果是父类(非接口 )和接口中有相同签名的方法,编译器不会报错,在非明确指定的情况下,会直接调用父类中的方法,而kotlin中则不行:

这里写图片描述

这里会有错误提示:方法f有多个实现

如果要解决覆盖冲突,就必须重写 方法:

class RR(var age: Int?, var name: String) : KK(), II {
    override fun f() {
        super<KK>.f()
        super<II>.f()
    }
}

abstract class KK {
    open fun f(): Unit {

    }
}

interface II {
    fun f(): Unit {

    }
}

更 深一层的是,kotlin接口中定义的非抽象方法,并不是引用了java8新特性default,而是有它自己的实现:

public interface II {
   void f();

   public static final class DefaultImpls {
      public static void f(II $this) {
      }
   }
}

kotlin在处理接口中的常规方法时,其实是新建了静态内部类,然后定义了静态方法,并将 this 作为参数传入方法中,因此如果不重写方法实现,编译器将不能确定如何调用。

类似这种的情况 还有很多,并不是说 哪种方法更好,在代码量的方面,kotlin要少的 多,但如果自身有比较长时间的java开发经验,然后再使用kotlin,在一些细节上可能会有记忆冲突。
因此在一些类上,比如数据类时使用kotlin,减少代码的编写,在结构较为复杂的类上,直接使用java,毕竟对于两种这么”大“的语言,即便可以做到100%互用,但细枝末节的地方肯定很多。


二、功能增强

Kotlin中对很多java功能进行了增强,来保证语言的高效和简洁

1、if语句增强

java中if语句主要用于分支处理,不过其算是语句块,而现在kotlin让其具有了返回值,然后配合 in ,! in , is ,! is 操作符,可以保证条件语句很简单,比如这样的对比:

java语句:

public void f(int a) {
    int b;

    if (a < 0 && a > -2) {
        b = a - 1;
    } else {
        b = a + 1;
    }
}

对应的Kotlin语句就变得特别简单:

fun f(a: Int) {
    val b = if (a in -2..0) a - 1 else a + 1
}

因为kotlin中的if和else有了返回值,那么java中三目运算符就发生了变化:

private fun f(a: Float?): Float {
    return a ?: 11F
}

此时表示:如果a为null,那么返回浮点数11,如果a不是null,则返回a,跟这个是一个意思(强行把表达式变复杂一些):

private fun f(a: Float?): Float {
    return a.let {
        if (a == null) {
            11F
        } else {
            a
        }
    }
}

2、for增强

for增强和if有些相似,因为 in操作 的存在,使得循环遍历很方便,具体添加的实用操作包括:

  • in,step,downTo,until
  • 数组和集合对象(map未实现Collections接口)的 indices 属性以及 withIndex 方法

例如这样:

val arrayOf = arrayOf(1, 2)
for (value in arrayOf) {
    println("${arrayOf.indexOf(value)}:  $value")
}
val arrayOf = arrayOf(1, 2)
for ((index,value) in arrayOf.withIndex()) {
    println("$index:  $value")
}
val arrayOf = arrayOf(1, 2)
for (index in arrayOf.indices) {
    println("$index:  ${arrayOf[index]}")
}

可以以多种形式实现遍历;

但是,kotlin删除了java或其他语言的 for(exp;exp;exp) 方式,这会在多重循环时很不适应,现在得使用这种区间的形式,可能刚开始会 转变不了风格:

fun ssort(vararg data: Number) {
    // 数据源长度
    val length = data.size

    for (i in 0 until length) {
        for (j in 0 until length - i) {
            // TODO 其他操作
        }
    }
}

3、操作符扩展

java中操作符的功能是定义好的,但在kotlin中,操作符可以自定义,这个功能我在初见时,以为像编译原理那样,可以当kotlin的造物主了。

虽然说看过生成文件后发现没那么变态,实现方式也不复杂,但着实为对种“逆天”的功能表示吃惊,语言设计者的脑洞真大:

操作符定义也比较简单,指定 operator 关键字就好了,像下面这样:

先定义一个类,有一个成员value,类型为:String

class RR {
    var value:String="null"
}

重定义 “ + ” 号所做的操作,可以在右侧和 Any?类型的数据相加;这里只是添加了额外的字符串,并且如果attr类型为null,则使用字符串 “xx”

operator fun RR.plus(attr: Any?): String {
    return this.value + " | " + (attr ?: "xx")
}

然后将RR对象右侧 + 上一个对象,或者 null

@Test
fun addition_isCorrect() {
    val s = RR() + Any()
    val ss =RR() + null
}

查看最后的结果:

这里写图片描述

结果确实像预期那般,我们可以查看一下到底对RR对象的 + 进行了什么处理,还是查看上面这段代码生成class文件后反编译获得的java源码:

@Test
public final void addition_isCorrect() {
   String s = InsertSortKt.plus(new RR(), new Object());
   String ss = InsertSortKt.plus(new RR(), (Object)null);
}

然后查看 InsertSortKt.plus 方法,转到我们重定义 + 操作所在的kotlin类(同样是class文件反编译用来查看的java源码)

@NotNull
public static final String plus(@NotNull RR $receiver, @Nullable Object attr) {
   Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
   StringBuilder var10000 = (new StringBuilder()).append($receiver.getValue()).append(" | ");
   Object var10001 = attr;
   if (attr == null) {
      var10001 = "xx";
   }

   return var10000.append(var10001).toString();
}

确实没有什么复杂操作,只是把 +操作 左右两侧的 RR 对象和 Any对象(转成java后变成Object对象)当作两个参数传入静态plus方法,然后执行固有逻辑而已

4、字符串扩展

java语言在进行 字符串拼接时,一般会有几种方式:

String a = "1";

//使用加号进行拼接
String b = a + "2" + 3 + "7";

//使用String.format进行处理
String.format(Locale.CHINA, "%s2%daaa", a, 1);

//使用 StringBuilder或者 buffer来拼接
new StringBuilder().append(a).append(2).append(b);

在生成 class时,编译器会进行优化,例如会将字符串相加变为StringBuilder形式。

单说上面几种情况,要么很复杂,要么很不美观,kotlin中增强了字符串拼接的 能力:

var a = "11"
var b = 22
println("第一个:$a 第二个:${b.toString()}")

这种方式在进行jsp等开发时,会经常用到,这算是一个很实用很棒的增强了。

5、方法扩展(方法重载)

在java中很多时候,有时创建同一对象时,可以传入不同的参数,因此需要进行重载,尤其在Android中,自定义View时,构造方法就需要传入不同的参数,有些参数可传可不传,这时候使用kotlin就比较舒服了:

对于构造函数和普通函数来说,都可以这样操作:

fun bbb(at1: Int = 99, at2: String, at3: String = "ssss") {
}

比如这里,形参at1和at3都有了默认值,那么调用该方法时候就可以选择性不传,调用时使用 ”=“ 等号指定参数名即可:

bbb(at1 = 11,at2 ="22")

这里只传入第一和第二参数(第一个参数因为已经有了默认值,可传可不传);就可以调用成功。

我们可以参考生成的字节码来看具体是如何实现的:

public final void bbb(int at1, @NotNull String at2, @NotNull String at3) {
   Intrinsics.checkParameterIsNotNull(at2, "at2");
   Intrinsics.checkParameterIsNotNull(at3, "at3");
}

// $FF: synthetic method
// $FF: bridge method
public static void bbb$default(RR var0, int var1, String var2, String var3, int var4, Object var5) {
   if ((var4 & 1) != 0) {
      var1 = 99;
   }

   if ((var4 & 4) != 0) {
      var3 = "ssss";
   }

   var0.bbb(var1, var2, var3);
}

编译器在生成一个全参的方法后,会多生成一个 $default 的方法,静态,第一个参数用来传入this,接下来三个参数与全参方法的参数相同,第四个参数是一个默认值取位值,来标记之前的三个参数哪些需要取默认值,最后一个参数这里不用。

上面的对这个方法调用的代码:bbb(at1 = 11,at2 =”22”) 对应的java源码为:

bbb$default(this, 11, "22", (String)null, 4, (Object)null);
  • this不说,表示实例本身,
  • 11为第一个参数的值,会覆盖掉默认值99
  • ”22“为第二个参数的值
  • (String)null为第三个参数的默认值(对于一些对应java的基本包装类如Int等,会取默认的字面量如0等,不过这里传值不会影响实际结果)
  • 4 表示默认值取值位为 :0x00000004,前三个字节,位上都是0,最后一个字节为:00000100;
    从右开始计算,第一第二位为0,表示不需要设置默认值;第三位为1,表示需要将var3设默认值为:”ssss“

最后就是使用三个参数值参与到方法的调用中。

一般来说,java中方法参数个数是没有什么限制的,那么如果参数数量超过了32,Int类型无法表示超过32位该怎么处理呢?

其实很简单,一个Int类型无法表示,那多几个Int类型数据就好了。

这里写图片描述

像这种超过了32个参数时,会用var36和var37两个Int类型来表示默认值取值位

三、高级特性

kotlin参考其他语言,添加了很多新的特性,至少在形式上突破了java固有限制

1、动态添加方法属性

在java中如果想要向某个类添加方法,只能去继承该类,然后添加方法;
这必然会导致类的数量增加,并且自定义子类在某些情况下并不足以满足需求;

kotlin中新特性便是可以为现有类动态添加属性和方法,代码大概是这样:

class RR {
    var value: String = "null"
}

fun RR.newMethod(): Unit {
    println("new method")
}

这样可以就可以为RR类动态的添加newMethod方法,该方法的作用域是:

使用的 话跟普通的 方法 调用一样,一般编译器会智能统计所有 新添加 的方法,方便代码调用;

之前已经提过,kotlin最后还是转成字节码的,那么这样动态添加属性方法是怎么实现的呢,我们同样参考字节码信息:

public final class RRKt {
   public static final void newMethod(@NotNull RR $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      String var1 = "new method";
      System.out.println(var1);
   }
}

字节码中赫然出现这些东西 ,也就是说,我们自定义一个文件时候,编译器帮助我们自动生成了 一个 **kt.class 的类,动态添加的属性或者方法,会以静态方法的形式实现,第一个参数为对象本身,该静态方法中就可以调用类方法了;

对于动态添加属性来说也是如此,先在动态添加 方法 的地方添加一个新的属性;
动态添加属性时要求,如果是可变属性,则必须同时提供get和set方法,这个具体原因在看过下面的例子后自然就会明白:

class RR {
    var value: String = "null"
}

fun RR.newMethod(): Unit {
    println("new method")
}

var RR.newField: String
    set(value) {
        this.value = "newField"
    }
    get() {
        return this.value ?: "null"
    }

可以看到,这里新添加了一个成员域:newFieldString类型,同时提供了get和set方法,然后看字节码后转成的 java代码:

public final class RRKt {
   public static final void newMethod(@NotNull RR $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      String var1 = "new method";
      System.out.println(var1);
   }

   @NotNull
   public static final String getNewField(@NotNull RR $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      String var10000 = $receiver.getValue();
      if (var10000 == null) {
         var10000 = "null";
      }

      return var10000;
   }

   public static final void setNewField(@NotNull RR $receiver, @NotNull String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(value, "value");
      $receiver.setValue("newField");
   }
}

生成的代码中多出来两个静态方法:get*** 以及 set*** (***是新添加的 成员域的 名称)
默认set方法无返回值get方法返回值为动态添加的成员域的类型

综上来看,kotlin语言中动态添加属性和成员域的功能,使用java语言同样是可以类似的模拟实现的,但肯定没有kotlin添加这么方便。

也因为如此,这种动态添加成员域的方法肯定会存在诸多限制,比如:

  • 在kotlin中,同一包下的不同类如果对同一类动态添加相同方法签名的 方法,会直接冲突
    这里写图片描述

    但事实上,在java中在两个类中定义两个相同名称的静态方法完全是可以的。

  • 以上面的代码来说,编译器自动生成了 以 kt 为后缀的 新的类,但如果使用java原生语言,在做到类似“动态添加方法”的同时,可以保证类的数量没有增加,并且也不需要生成多余的静态方法

当然,如果目的是简单方便,就另当别论了,至少在代码编写的时间上,用kotlin可以省去将近一半。

2、对象析构

第一次见kotlin中的对象析构确实有些傻眼,对于属性来说,竟然还有这种操作方式:

data class RR(var age: Int?, var name: String, var sex: Boolean)

fun aaa(): Unit {
    val rr = RR(1, "2", true)
    var (age, name, sex) = rr
}

同时声明并为变量赋值(不支持为已有变量赋值)

这种方式着实有些厉害,第一感觉是:以后数据查询方便多了,毕竟这种赋值方式足够强大,当然看过实现后其实并没有那么夸张,不过相对java来说,已经很新颖了

代码对应的java源码:

public final class RRKt {
   public static final void aaa() {
      RR rr = new RR(1, "2", true);
      Integer var1 = rr.component1();
      String var2 = rr.component2();
      boolean sex = rr.component3();
   }
}

RR类对应源码下面再列出,这里只看赋值的部分;
调用了RR对象的 component[\d] 方法依次来为变量赋值。

这个实际上是数据对象类在编译器处理的时候,会自动的 帮助添加 component[\d] 方法:

public final class RR {
   //...

   @Nullable
   public final Integer component1() {
      return this.age;
   }

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

   public final boolean component3() {
      return this.sex;
   }

   //...
}

实际上上面所说的增强for循环,在遍历时也有用到这一点:

fun aaa(): Unit {
    var array = intArrayOf(1, 2, 3)
    for ((index, value) in array.withIndex()) {
        println("$index $value")
    }
}

其中(index,value)就是 IndexedValue 对象的析构

public data class IndexedValue<out T>(public val index: Int, public val value: T)

3、函数式编程

kotlin语言最大的宣传点除了空值处理外,就是函数式变成,在jdk8之前,java需要通过其他插件来实现形式上的lambda。

也就是说,kotlin对于函数式编程的实现,底层并未使用 invokeDynamic 字节码。

而相比于java8的lambda,在形式上,kotlin支持将函数赋值给变量,这点不像java,更偏向于javascript:

var f: (Int) -> Int = { i: Int ->
    i * i * attr
}

println(f(3))

这里会发现 变量f 的类型为:(Int)-> Int

java原生是没有这种类型的,可以通过反编译查看对应的java代码:

Function1 f = (Function1)(new Function1() {
   // $FF: synthetic method
   // $FF: bridge method
   public Object invoke(Object var1) {
      return this.invoke(((Number)var1).intValue());
   }

   public final int invoke(int i) {
      return i * i * RR.this.getAttr();
   }
});
int var2 = ((Number)f.invoke(3)).intValue();
System.out.println(var2);

java源码可以看到,函数类型变成了 Function1 对象

/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}

这样不容易看清楚,这里把 Function1 对象也变成java代码:

public interface Function1 extends Function {
   Object invoke(Object var1);
}

一个接口,父类为Function接口,每个函数类型(如变量f)在编译时会动态生成一个内部类,然后使用内部类去调用invoke方法,这点和java8之前实现lambda的方式类似。

具体可以参考:class文件(经反编译后显示的内容)(从class文件角度,分析jdk8支持的lambda和一般内部类)

实际上,在Java8以后,jdk已经新添加了许多功能,lambda表达式已经可以满足一般情况的需要了。

4、高阶函数

kotlin语言的特点就是简单、精巧,相对于java的沉重,kotlin精简了很多操作,所以看起来会比较”飘“

配合着lambda,kotlin可以做到复杂问题简单化,很多冗余的操作将由编译器自动完成;

具体可以参考其他博客:kotlin高阶函数

总的来说,在编写数据类,工具类或者提供库文件时,使用kotlin会节省很多时间;
但感觉在泛型以及面向对象的方面,在适应了java的情况下 ,会有些别扭。
且由于java8的出现,一般的函数式编程也可以实现。

因此个人觉得,两者混合开发就好

猜你喜欢

转载自blog.csdn.net/lovingning/article/details/80279461