美文网首页android开发技巧Kotlin in AndroidAndroid开发
Kotlin 学习笔记(二)—— 数据类、枚举类、循环、常用集合

Kotlin 学习笔记(二)—— 数据类、枚举类、循环、常用集合

作者: 修之竹 | 来源:发表于2021-07-04 17:29 被阅读0次

    感悟:不知不觉一年就过了一半··· 年初定的目标完成了一半了吗?eee... 趁着写年中总结,好好反省反省 (笑Cry.jpg)。现在还来得及,童鞋们继续努力!

    在上篇笔记中,我们对 Kotlin 的基本类型、关键字、类与对象,以及与 Java 之间互调的内容有了一些认识,这篇笔记来看看 Kotlin 中几种特殊的类,以及集合相关的常用操作。

    1. Kotlin 数据类

    Kotlin 语言中有个特殊的类叫 数据类,这个类型是 Kotlin 专门用来表示一个 Java Bean 的,为啥这么说?因为它帮我们自动生成了一个数据类应该有的方法。比如:getter/setter 方法、toString()、hashCode()、equals()、copy() 方法。举个栗子:

    // code 1
    data class Book(val id: Int, val name: String) {
    }
    

    Kotlin 代码就这么点,然后再看看为我们生成的 Java 代码:(如何查看生成的 Java 代码?找到 AndroidStudio 的 Tools 选项,依次选 Kotlin、show Kotlin ByteCode,然后在新出现的代码窗口点击 Decompile 就可以看到生成的 Java 代码了)

    // code 2
    public final class Book {
       private final int id;
       @NotNull
       private final String name;
    
       public final int getId() {
          return this.id;
       }
    
       @NotNull
       public final String getName() {
          return this.name;
       }
    
       public Book(int id, @NotNull String name) {
          Intrinsics.checkNotNullParameter(name, "name");
          super();
          this.id = id;
          this.name = name;
       }
    
       public final int component1() {
          return this.id;
       }
    
       @NotNull
       public final String component2() {
          return this.name;
       }
    
       @NotNull
       public final Book copy(int id, @NotNull String name) {
          Intrinsics.checkNotNullParameter(name, "name");
          return new Book(id, name);
       }
    
       // $FF: synthetic method
       public static Book copy$default(Book var0, int var1, String var2, int var3, Object var4) {
          if ((var3 & 1) != 0) {
             var1 = var0.id;
          }
    
          if ((var3 & 2) != 0) {
             var2 = var0.name;
          }
    
          return var0.copy(var1, var2);
       }
    
       @NotNull
       public String toString() {
          return "Book(id=" + this.id + ", name=" + this.name + ")";
       }
    
       public int hashCode() {
          int var10000 = this.id * 31;
          String var10001 = this.name;
          return var10000 + (var10001 != null ? var10001.hashCode() : 0);
       }
    
       public boolean equals(@Nullable Object var1) {
          if (this != var1) {
             if (var1 instanceof Book) {
                Book var2 = (Book)var1;
                if (this.id == var2.id && Intrinsics.areEqual(this.name, var2.name)) {
                   return true;
                }
             }
             return false;
          } else {
             return true;
          }
       }
    }
    

    想不到吧?一行 Kotlin 竟然帮我们干了这么多事!这也说明用 Kotlin 确实能减少代码量,提升 Coding 效率。再看看 Kotlin 数据类的特点。

    首先,data class 是一个 final 类,说明 data class 是无法被继承的,哪怕在它前面加 open 关键字也不行。细心的同学可能发现了:这个 Book 并没有 setter 方法呀?没错,那是因为之前在写 Kotlin 代码时,把 id 和 name 两个参数设置为 val 不可变变量了,如果改成 var 再次生成一下,你就会发现有 setter 方法了。

    然后,其他的 hashCode、toString、equals、copy 方法都是常规的一些操作,当然,也可以在 data class 里面重写这些方法,达到自己想要的效果。

    再看一眼,发现还有两个比较陌生的方法:component1() 和 component2(),和 getter 方法一样啊,为啥?其实这两个方法是 data class 用来解构的,方便用户直接拿到某个对象中的属性值,如下:

    // code 3
    val book = Book(1, "鲁迅文集")
    book.id = 23    // setter 方法
    val(bookId, bookName) = book    // 对 book 直接解构
    print("bookName = $bookName")    // 输出:“bookName = 鲁迅文集”
    

    如果还有第三个属性的话一样的,就会给你生成 component3(),以此类推。这两个 component 是 data class 自动生成的,如果普通的 class 也想这样解构,也可以,需要使用 operator 关键字,对类中的属性做一个关联:

    // code 4
    class Car(var brand: String, var price: Double) {    // 普通类 解构
        operator fun component1(): String {
            return this.brand
        }
        operator fun component2(): Double {
            return this.price
        }
    }
    

    operator 关键字可以用来重载操作符或者实现一个约定。这里就是实现了一个约定。这样写之后就可以像 data class 一样进行解构了。Kotlin 的这种解构方式用的比较多的地方是在 Map 数据结构中,它不需要像 Java 一样先拿到 Entry 对象,然后才能拿到 key 值和 value 值,直接用这种解构即可。

    // code 5
    val person = mapOf<String, String>("name" to "Jack", "age" to "23")
    for ((key, value) in person) {
        println("key: $key ==== value: $value")
    }    // (是不是比 Java 方便多了?)
    

    2. Kotlin 枚举类(密闭类)

    Kotlin 中也有枚举类,使用方法与 Java 的枚举类一样,此外,Kotlin 还有一个更加强大的枚举类,称为密闭类,用 sealed 关键字修饰。密闭类可以有自己的子类,而且可以扩展它的子类,例如给子类设置一些参数等,要知道,枚举类是不具有这种特点的:

    // code 6
    // Kotlin 密闭类(高级枚举类)
    sealed class Language {
        object English: Language()
        object Japanese: Language()
        class Chinese(var isDialect: Boolean): Language()    // 是否方言
    }
    

    3. Kotlin 循环写法

    这个就没啥可说了,直接上代码!

    // code 7
        for (i in 1..10) {    // 注意  x..y 表示的是 x<= i <= y,都是闭区间,且 x <= y,否则 i 为空
            print("$i ")    // 输出 1 2 3 4 5 6 7 8 9 10
        }
        for (i in 1 until 10) {    // 区间为 前闭后开
            print("$i ")    // 输出 1 2 3 4 5 6 7 8 9 
        }
        for (i in 10 downTo 1) {    // 递减循环
            print("$i ")    // 输出 10 9 8 7 6 5 4 3 2 1 
        }
        for (i in 1..10 step 2) {    // step 为步进长
            print("$i ")    // 输出 1 3 5 7 9 
        }
        repeat(10) {    // 高阶函数,大括号里表示的是 Lambda 表达式,传进的参数为循环执行这个表达式多少次,it 表示的是目前执行的是第几次,从 0 开始
            print(it)    // 输出 0123456789
        }
        // 平时用的比较多的还是取集合中的元素
        val list = listOf("I","am","Chinese")
        for (str in list) {
            print("$str ")
        }
        for ((index, str) in list.withIndex()){
            print("第${index}元素是$str")
        }
    

    4. Kotlin 常用集合

    在 Kotlin 中常用的集合类主要有 List:有序集合,可通过索引访问元素; Set:唯一元素集合,无重复元素的集合;Map:键值对集合,键是唯一的。这 3 种都是 集合接口,这些都和 Java 中的一样。

    Kotlin 的集合也可以根据是否可变分为两大类:可变集合 和 不可变集合。不可变集合就是集合中的元素是不可以被修改的,没有 add、remove 等写操作的方法。一般在声明的时候就定义了集合是否可变,可变的集合声明就是 mutableListOf()、mutableSetOf()、mutableMapOf(). 不可变的集合声明是 listOf()、setOf()、mapOf(). 当然,如果在声明的时候就知道集合里的元素,就可以使用这些函数进行初始化:

    // code 8
    val list = listOf("a", "b", "c", "d")    // 不可变集合声明
    val mutableMap = mutableMapOf("name" to "Tom", "age" to "99")    // 可变集合声明
    

    创建空集合的函数用的比较少,分别是 emptyList()、emptySet()、emptyMap() 。而且创建出来的都是只读类型的集合,又不能往里面放元素,所以用的少。

    用的较多的当然还是构造函数,以 List 作为说明,Set 和 Map 以此类推。

    // code 9
    val doubled = List(3, { it * 2 })    // 通过索引 index 初始化集合,初始化了元素个数为 3 的不可变链表。如果需要可变,则用 MutableList
    println(doubled)    // 输出:[0, 2, 4]
    
    // 创建具体类型的集合,例如 ArrayList 或 LinkedList,其实这些最后都是调用的 Java 中相应集合类
    val linkedList = LinkedList<String>(listOf("one", "two", "three"))    // 创建链表
    

    Kotlin 当然也有数组集合 Array,但是用的并不是很多,基本上以上 3 种集合都可以胜任了。Array 的声明:

    // code 10 
    val array = Array(5, { it + 1 })    // 初始化长度为 5 的数组 [1,2,3,4,5]
    array[0] = 99    // array 可修改元素值,修改后为 [99,2,3,4,5]
    

    与 code 9 中的 List 不一样的是,Array 的这种初始化后的 array 对象,是可以对数组中的元素做修改的,但是其他的 List、Set、Map 都不行。

    5. Kotlin 集合操作符

    Kotlin 为集合扩展了许多操作符,而且这些操作符还支持链式调用,非常方便。相比于 RxJava 来说,代码量会少很多。照例还是举个栗子吧。下面的例子是将一段字符进行加密转换的操作,先看看 RxJava 是怎么处理的。

    // code 8
    public class RxJavaExample {
        public static void main(String[] args) {
            final String[] a = new String[]{"4","0","7","i","f","w","0","9"};
            final Integer[] index = new Integer[]{5,3,9,4,8,3,1,9,2,1,7};
    
            Observable.just(index)    // 1. 传入 index 数组
                    .flatMap(new Function<Integer[], ObservableSource<Integer>>() {
                        // flatMap 可将集合拆成一个个元素返回
                        @Override
                        public ObservableSource<Integer> apply(Integer[] integers) throws Throwable {
                            // 2. 调用 flatMap 方法将数组中的 Integer 元素一个个取出
                            return Observable.fromArray(integers);
                        }
                    })
                    .filter(new Predicate<Integer>() {
                        @Override
                        public boolean test(Integer i) throws Throwable {
                            // 3. 调用 filter 方法,将小于 a 数组长度的 元素 过滤出来
                            // 得到 5,3,4,3,1,2,1,7
                            return i < a.length;
                        }
                    })
                    .map(new Function<Integer, String>() {
                        // map 可将传入的参数类型,进行转换后输出,这里就是 Integer 转为 String 类型
                        @Override
                        public String apply(Integer integer) throws Throwable {
                            // 4. 调用 map 方法,将得到的一串数字再依次作为 integer 参数输入 a[integer] 中
                            // 得到 "w","i","f","i","0","7","0","9"
                            return a[integer];
                        }
                    })
                    .reduce(new BiFunction<String, String, String>() {
                        @Override
                        public String apply(String s, String s2) throws Throwable {
                            // 5. 调用 reduce 方法,将 得到的字符串两两组合
                            // 得到 "wifi0709"
                            System.out.println("中间结果:s = " + s + "  s2 = " + s2);
                            return s + s2;
                        }
                    })
                    .subscribe(new Consumer<String>() {
                        @Override
                        public void accept(String s) throws Throwable {
                            System.out.println("最终结果:" + s);
                        }
                    });
        }
    }
    

    RxJava 中有许多的操作符,这里只列举了几个,它们的用法在注释里都可以找到。其中 reduce 的操作我之前是有点不清楚,找到的答案都是说,传入的第一个变量和第二个变量经过处理后,依次两两组合处理,最终得到结果。但到底是怎么个“两两组合处理”?在这里我打印出了中间结果就一目了然了:

    图1 reduce 处理的中间结果

    再来看看 Kotlin 的写法:

    // code 9
    object CollectionOperator {
        private val a = arrayOf("4", "0", "7", "i", "f", "w", "0", "9")
        private val index = arrayOf(5, 3, 9, 4, 8, 3, 1, 9, 2, 1, 7)
        fun exampleOutput() {
            index
                .filter {
                    it < a.size
                }
                .map {
                    a[it]
                }
                .reduce { s, s2 ->
                    "$s$s2"
                }
                .also {
                    println("最终结果:s = $it")
                }
        }
    }
    

    没想到,Kotlin 的好多集合操作符跟 RxJava 命名都是一样的!而且更好用,比如 Kotlin 不用 flatMap ,自动就将集合中的元素拆成一个个的了,filter 里面的 it 就是代表的集合中的一个个的元素。map、reduce 这不就是借鉴的 RxJava 吗?而且 Kotlin 的 Lambda 表达式使得代码更为简洁。

    除了上述的集合操作符,常见的还包括下面的几个操作符,直接在代码中讲解更清楚:

    // code 10
    val test = listOf("2","3","x","2","g","9","k","o")    // 测试集合
    // 1、contains:判断是否含有某元素
    println(test.contains("x"))    // 输出:true
    println(test.contains("w"))    // 输出:false
    // 2、elementAt:返回对应的索引元素,越界会抛出 IndexOutOfBoundsException
    println(test.elementAt(3))    // 输出:"2"
    // 3、firstOrNull :找出满足条件的第一个元素,若无则返回 null
    println(test.firstOrNull { it == "3" })    // 输出:"3"
    // 4、indexOf:返回指定元素的索引,若无则返回 -1
    println(test.indexOf("k"))    // 输出:6
    // 5、singleOrNull:返回满足条件的单个元素,若没有元素满足条件或不止一个元素满足,则返回 null
    test.singleOrNull{
        it == "2"
    }.also {
        println(it)    // 输出:null
    }
    // 6、any :集合中任意一个元素若满足条件,则返回 true;否则返回 false
    println(test.any { it == "o" })    // 输出:true
    
    val list = listOf(23,-1,4,6,888,34)
    // 7、all :集合中所有元素都满足条件,则返回 true;否则返回 false
    println(list.all { it < 1000 })    // 输出:true
    // 8、none:集合中所有元素都不满足条件,则返回 true;否则返回 false
    println(list.none { it - 100 > 100 })    // 输出:false
    // 9、count:返回集合中满足条件的元素个数
    println(list.count { it > 0 })    // 输出:5
    // 10、reduce:集合中第一项和第二项进行处理,得出的结果再和第三项进行处理,一直处理到最后一个元素
    println(list.reduce { v1, v2 ->
        v1 + v2
    })    // 输出:954
    
    // 11、filter 操作符,过滤,Lambda 表达式是过滤的条件
    println(list.filter { it > 0 })    // 输出:[23, 4, 6, 888, 34]
    // 12、filterNot 与 filter 是互补的,输出 filter 过滤掉的
    println(list.filterNot { it > 0 })    // 输出:[-1]
    // 13、filterNotNull:过滤掉 null 的元素
    println(listOf(null, 3, 5, null, 8).filterNotNull())
    // 14、take:取前 n 个元素
    println(list.take(3))    // 输出:[23, -1, 4]
    // 15、mapIndexed:带 index 索引信息的 map
    println(listOf(35,61,116,74).mapIndexed{ index, i ->
        (i+index).toChar()
    })    // 输出:[#, >, v, M]
    // 16、mapNotNull:执行map转换前需要过滤掉 null 的元素
    println(listOf(null, 33, 55, null, 88).mapNotNull {
        it?.toChar()
    })    // 输出:[!, 7, X]
    // 17、groupBy:分组操作符。这里就是将 list 分为偶数奇数两组,返回一个 Map
    println(list.groupBy { it % 2 == 0 })    // 输出:{false=[23, -1], true=[4, 6, 888, 34]}
    // 18、reversed:生成一个与输入集合反序的集合
    println(list.reversed())    // 输出:[34, 888, 6, 4, -1, 23]
    // 19、sorted:生成一个升序排列集合,原集合不变
    println(list.sorted())
    // 20、sortedDescending:生成一个降序排列集合,原集合不变
    println(list.sortedDescending())
    // 21、sortedBy:自定义排序,主要针对 Pair 进行排序
    val pairList = listOf(1 to "A", 2 to "B", 5 to "C", 3 to "D")
    println(pairList.sortedBy {
        it.first
    })    // 输出:[(1, A), (2, B), (3, D), (5, C)]
    println(pairList.sortedBy {
        it.second
    })    // 输出:[(1, A), (2, B), (5, C), (3, D)]
    

    与 RxJava 类似,Kotlin 也有一个 flatMap 操作符,有许多同学不禁会问了:map 和 flatMap 有啥区别啊?可以根据名称来记,flatMap 有 flat,flat 英文意思是:平的,扁平的;使扁平,它有把集合数据拆开,铺展开的操作。再加个实例说明:

    // code 11
    val multipList = listOf(listOf(1,9,4,6), listOf("R","U","OK"))
    println(multipList.map { it })    // 输出:[[1, 9, 4, 6], [R, U, OK]]
    println(multipList.flatMap { it })    // 输出:[1, 9, 4, 6, R, U, OK]
    

    所以还是那句话:纸上得来终觉浅,绝知此事要躬行

    更多 Kotlin 笔记可以查看
    上一篇:Kotlin 学习笔记(一)—— 基本类型、函数、lambda、类与对象的写法
    下一篇:Kotlin 学习笔记(三)—— 作用域函数、inline 关键字、反引号用法以及对象比较等内容

    参考文献

    1. 张涛; 极客时间 Kotlin 系列课程
    2. Kotlin 官方中文文档https://www.kotlincn.net/docs/reference/collections-overview.html
    3. 全汪汪; Kotlin-Map和flatMaphttps://www.jianshu.com/p/696d307c2c59
    4. 东海陈光剑; Kotlin 中的集合类排序https://blog.csdn.net/universsky2015/article/details/83593126

    ps. 赠人玫瑰,手留余香。欢迎转发分享,你的认可是我继续创作的精神源泉。

    相关文章

      网友评论

        本文标题:Kotlin 学习笔记(二)—— 数据类、枚举类、循环、常用集合

        本文链接:https://www.haomeiwen.com/subject/nkpzyltx.html