从java的角度看kotlin特性(一)
java之所以强大,是因为自身设定的时候,需要先经过一个编译过程,生成class文件。然后class文件再经由java虚拟机(JVM),解释执行。(这里以最初的情况为基准,不考虑JIT、ART等技术)java语法规范和JVM规范完全是两个分离的部分,这也是Kotlin等其他JVM语言成功的基础。事实上,JVM语言还有很多,但好像只有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"
}
可以看到,这里新添加了一个成员域:newField,String类型,同时提供了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的出现,一般的函数式编程也可以实现。
因此个人觉得,两者混合开发就好
更多推荐
所有评论(0)