Kotlin使用体验及理解1

接触Kotlin前前后后也有大半年了,和Java相比最直观的感受就是两点:1、API功能更全面、更简洁。Kotlin除了能提供Java API里所有的功能,还在此基础上添加了新的一些常用的方法,而这些方法之前使用Java时很多需要自己实现。 2、协程,它提供了比线程更小一级的执行单位,使CPU利用率更高了。

下面这些记录主要是我复习和巩固用的,也趁机把它写成博客,对于已经很熟悉Kotlin的同学来说没多大价值。

1、属性Property和字段field的区别

这里一直是我很迷惑的地方,直到开始研究Kotlin编译后的Java代码,在做它们的对比并理解Kotlin的思想之后才逐渐明白。字段是数据存储位置,是一小块内存,属性是一种对外暴露的访问规则,本质就是怎么访问;属性不一定有字段。在Java中 int age = 18,在内存中就真的会有一个4字节的空间来存储这个字段,当访问user.age的时候其实就是真的去读了这个内存值,这就是字段;在Kotlin中,定义一个 var age = 18,也有字段,但是如果是定义一个

var aget:Int

get() = (1..100).random()

set(value) {

println("设置的值是:$value")

}

可就没有字段了,这种情况就非常体现出字段和属性的区别了,属性------一种访问规则。在这个情况下,你读取age值,它就通过get方法返回一个临时的值,你通过set设置值,其实真实逻辑可以随便实现,并不一定要真的设置一个值。先来看下Kotlin代码编译后的情况吧

复制代码
//Kotlin源码
class User{
    var age:Int = 8
    val name:String = "Tom"
    var address: String
        get() = "深圳市"
        set(value) {
                println("设置了一个新的地址:${value}")
        }
}

//编译后的Java代码
public final class User {
    private int age = 8;

    @NotNull
    private final String name = "Tom";

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

    public final void setAge(int i) {
        this.age = i;
    }

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

    @NotNull
    public final String getAddress() {
        return "深圳市";
    }

    public final void setAddress(@NotNull String value) {
        Intrinsics.checkNotNullParameter(value, "value");
        System.out.println((Object) ("设置了一个新的地址:" + value));
    }
}

可知,age和name都有对应的字段,且age由于是var,还自动生成了get和set方法,name是val,所以自动生成了get方法,当读取age、name时其实就是在调用它们的get方法,当修改age时,其实就是在调用setAge方法;而address方法,由于我们手动提供了get/set,所以,也就没有字段了。那能不能这样定义呢

复制代码
var address: String ="龙华区"
    get() = "深圳市"
    set(value) {
            println("设置了一个新的地址:${value}")
    }

不能!Kotlin的规则,一旦 自己同时重写了 getter 和 setter,这个属性就不再是"默认访问器 + 默认字段"的模式了,没有字段可以存储这个"龙华区"。不过可以这样写

var address: String = "龙华区"

get() = field

set(value) {

field = value

println("设置了一个新的地址:$value")

}

field就是字段,你在get/set里使用到了字段,也就会自动生成一个字段了。

2、数据类data class

专门用来保存数据的类。与普通类相比,使用 data 关键字声明的类,编译器会自动为它生成几个标准且常用的方法,这省去了我们手动编写大量样板代码的麻烦。

一句简单的 data class KotlinUser(var name:String,var age:Int){ var id:Int = 0 },编译后就是这样的

复制代码
public final class KotlinUser {

    @NotNull
    private String name;
    private int age;
    private int id;

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

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

    @NotNull
    public final KotlinUser copy(@NotNull String name, int age) {
        Intrinsics.checkNotNullParameter(name, "name");
        return new KotlinUser(name, age);
    }

    public static /* synthetic */ KotlinUser copy$default(KotlinUser kotlinUser, String str, int i, int i2, Object obj) {
        if ((i2 & 1) != 0) {
            str = kotlinUser.name;
        }
        if ((i2 & 2) != 0) {
            i = kotlinUser.age;
        }
        return kotlinUser.copy(str, i);
    }

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

    public int hashCode() {
        int result = this.name.hashCode();
        return (result * 31) + Integer.hashCode(this.age);
    }

    public boolean equals(@Nullable Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof KotlinUser)) {
            return false;
        }
        KotlinUser kotlinUser = (KotlinUser) other;
        return Intrinsics.areEqual(this.name, kotlinUser.name) && this.age == kotlinUser.age;
    }

    public KotlinUser(@NotNull String name, int age) {
        Intrinsics.checkNotNullParameter(name, "name");
        this.name = name;
        this.age = age;
    }

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

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

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

    public final void setAge(int i) {
        this.age = i;
    }

    public final int getId() {
        return this.id;
    }

    public final void setId(int i) {
        this.id = i;
    }
}

编译器自动为其改写了equals()、hashcode()、toString()、copy()。Kotlin中的 == 不同于Java,在Java中 == 对于基本数值,比较的是它们的值,对于引用对象,则比较的是引用地址,但Kotlin中,对于data class来说 == 比较的是主构造参数是否一一相等,对于非data class来说, == 比较的是 equals()。Kotlin中对于引用的比较则是用 === 。

复制代码
    val user1 = KotlinUser("H",2)
    user1.id = 1
    val user2 = KotlinUser("H",2)
    user2.id = 3
    println(user1.equals(user2))   //结果为true
    println(user1 == user2) //结果为true

    val a =A("H") //A是非data class 
    val b =A("H")
    println(a==b)  //false
    println(a.equals(b)) //false
3、密封类 sealed class

是 Kotlin 中一个用于限制类层次结构的特殊抽象类。它的核心作用是:一个类只能有有限的、已知的子类型,并且这些子类型都定义在同一个文件内(Kotlin 1.5 之后放宽到同一个编译单元,但通常仍建议同文件)。

复制代码
sealed class NetworkResult {
    data class Success(val data: String) : NetworkResult()
    data class Error(val throwable: Throwable) : NetworkResult()
    object Loading : NetworkResult()
}

看起来跟枚举类很像,而Kotlin也确实存在枚举类,那么,有了枚举类,为什么又要设计一个密封类呢?看上面的代码就可以很明显地看出来,以往枚举类只能定义成统一的格式,如果想表达上述的同样的效果,则每个枚举值都得带有data、throwable,密封类则可以只带自己需要的。

4、伴生对象(companion object

伴生对象(Companion Object) 是 Kotlin 中用来替代 Java static 关键字的机制。它允许你在类内部定义一个属于类本身而不是实例 的对象。Kotlin 语言设计者 Andrey Breslav 明确指出:Kotlin 中不存在类的 "静态成员" 概念 。在纯面向对象范式中,所有行为和状态都应属于对象,而static成员本质上是不属于任何对象的全局实体,破坏了面向对象的封装性和一致性。

Java 号称"一切皆对象",但 static 打破了这个承诺:

(1)static 方法/字段不属于任何实例,而是属于"类本身"------但"类本身"在 JVM 中不是一个对象(虽然 Class 对象存在,那是另一回事)。

(2)static 方法不能被继承、不能被重写、不能作为参数传递,也不能作为实现接口的方法。它更像是过程式语言中的全局函数,只是借用了类的命名空间。

Kotlin 的设计者希望消除这种割裂:每个函数、每个属性都必须属于一个对象。当你需要在类级别(不依赖实例)提供功能时,你其实是在定义一个属于类本身的一个对象------这就是伴生对象。也就是,kotlin的设计者认为类不该有属性和方法,每个属性和方法都必须属于某个对象,所以对于Java中static的属性和方法就要通过一个对象来实现,不过既然是一个对象,那么就得有这个对象的类,这个类是什么呢?看如下代码

复制代码
//Kotlin源码
class HelloKotlin{
    companion object{
        const val NAME = "test"
        val ADDRESS = "深圳"
        var age = 18

        fun show(info:String){
            println(info)
        }
    }
}


//编译后生成的Java代码
public final class HelloKotlin {

    @NotNull
    public static final String NAME = "test";

    @NotNull
    public static final Companion Companion = new Companion((DefaultConstructorMarker) null);

    @NotNull
    private static final String ADDRESS = "深圳";
    private static int age = 18;
}


//编译后还自动生成了一个HelloKotlin$Companion.class文件,里面的代码如下
public final class HelloKotlin$Companion {
    public /* synthetic */ HelloKotlin$Companion(DefaultConstructorMarker $constructor_marker) {
        this();
    }

    private HelloKotlin$Companion() {
    }

    @NotNull
    public final String getADDRESS() {
        return HelloKotlin.access$getADDRESS$cp();
    }

    public final int getAge() {
        return HelloKotlin.access$getAge$cp();
    }

    public final void setAge(int i) {
        HelloKotlin.access$setAge$cp(i);
    }

    public final void show(@NotNull String info) {
        Intrinsics.checkNotNullParameter(info, "info");
        System.out.println((Object) info);
    }
}

所以这个伴生对象的类就是由编译器自动生成的 XXX$Companion。

观察可发现伴生对象里,const val变成了类的public static final,这一点好理解;var 和 val 却都变成了private static ,而get/set方法却又放到了Companion类里作为了实例方法,看起来不伦不类的,这是什么设计?如果是按照Java的思路,那应该把var、val也都放到HelloKotlin作为public static,但是这样就违背了Kotlin任何方法、属性都应该属于对象的设计原则;如果按照Kotlin的思路,那就应该全都放到 HelloKotlin$Companion里,作为对象的实例字段和实例get/set方法才对,但是这样的话,Java调用Kotlin代码时就会变成HelloKotlin.Companion.age,而不是HelloKotlin.age或HelloKotlin.getAge(),这样的调用就会很奇怪(会疑惑Companion是什么,莫名其妙)。而之所以const val最终转换为public static final,是因为const val与Java的static final等效,这叫编译时常量,JVM对这类数据有一些特定的规则,为了能和JVM兼容,必须转换为public static final。总结一下就是,Kotlin为了和Java兼容,同时又为了尽量践行一切属性和方法都属于对象的设计原则,最终做出了如上的折中措施

不过Kotlin还是提供了 @JVMStatic 来提高与Java之间的互操作性,也更方便习惯Java思维的人使用。如下

复制代码
//Kotlin源码
class HelloKotlin{
    companion object{
        @JvmStatic
        var age = 18
    }
}

//编译后得到的Java源码
public final class HelloKotlin {

    @NotNull
    public static final Companion Companion = new Companion((DefaultConstructorMarker) null);
    private static int age = 18;

    public static final int getAge() {
        return Companion.getAge();
    }

    public static final void setAge(int i) {
        Companion.setAge(i);
    }
}

既然伴生对象是一个类的实例,那这个类是否也可以实现接口、继承其他类呢?可以。比如如下这样

class HelloKotlin{

companion object: InterfaceHou{

const val NAME = "test"

val ADDRESS = "深圳"

var age = 18

fun show(info:String){

println(info)

}

override fun haha() {

}

}

}

5、扩展属性/扩展方法

扩展方法和扩展属性让你能够"扩展"任何类(包括标准库的类、第三方库的类)而无需继承它们或直接修改它们的源代码。下面示例修改原生API里的ArrayList

复制代码
fun ArrayList<Int>.showSum(){
    var sum=0;
    for(i in this){
        sum += i
    }
    println(sum)
}

fun main() {
    val arrayList = ArrayList<Int>()
    arrayList.add(1)
    arrayList.add(2)
    arrayList.add(3)
    arrayList.showSum()
}

//编译后得到的Java代码如下
public static final void showSum(@NotNull ArrayList<Integer> arrayList) {
        Intrinsics.checkNotNullParameter(arrayList, "<this>");
        int sum = 0;
        Iterator<Integer> it = arrayList.iterator();
        Intrinsics.checkNotNullExpressionValue(it, "iterator(...)");
        while (it.hasNext()) {
            Integer next = it.next();
            Intrinsics.checkNotNullExpressionValue(next, "next(...)");
            int i = next.intValue();
            sum += i;
        }
        System.out.println(sum);
    }

    public static final void main() {
        ArrayList arrayList = new ArrayList();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        showSum(arrayList);
    }

其实原理就是生成了一个static方法,参数就是被扩展的类的一个对象,并且对象是作为第一个参数(如果还有其他参数,其他参数就排在后面)。

扩展属性类似

复制代码
var ArrayList<Int>.info:String
    get() = "元素个数:${this.size}"
    set(value) {}

fun main() {
    val arrayList = ArrayList<Int>()
    arrayList.add(1)
    arrayList.add(2)
    arrayList.add(3)
    arrayList.showSum(2)
    println(arrayList.info)
}

//编译后
public static final String getInfo(@NotNull ArrayList<Integer> arrayList) {
        Intrinsics.checkNotNullParameter(arrayList, "<this>");
        return "元素个数:" + arrayList.size();
    }

    public static final void setInfo(@NotNull ArrayList<Integer> arrayList, @NotNull String value) {
        Intrinsics.checkNotNullParameter(arrayList, "<this>");
        Intrinsics.checkNotNullParameter(value, "value");
    }


public static final void main() {
        ArrayList arrayList = new ArrayList();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        System.out.println((Object) getInfo(arrayList));
    }
相关推荐
勤劳的进取家1 小时前
传输层基础
运维·开发语言·学习·php
wangbing11251 小时前
Java处理csv文件总是丢数据
java·开发语言·python
Rust语言中文社区1 小时前
【Rust日报】2026-04-28 Pacquet:pnpm 的 Rust 重写版本
开发语言·后端·rust
modelmd2 小时前
研究C语言的hello world输出
c语言·开发语言·chrome
小小19922 小时前
vue 单页面请求
开发语言·前端·javascript
hhb_6182 小时前
JavaScript 本地存储与动态数据渲染实战案例
开发语言·javascript·ecmascript
淀粉肠kk2 小时前
【C++11】智能指针详解
开发语言·c++
kyriewen112 小时前
Next.js部署:从本地跑得欢,到线上飞得稳
开发语言·前端·javascript·科技·react.js·前端框架·ecmascript
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第21题:HashMap和Hashtable的区别是什么
java·开发语言·面试·哈希算法·散列表·hash table