ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ### Set集合介绍 #### Set集合怎么定义呢? 从概念角度去理解,set集合也叫set列表,存储的元素**无序不可重复**。 * 无序,存入和取出的顺序不一定一致([`LinkedHashSet`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-linked-hash-set/index.html)比较特殊,它保留元素插入的顺序); * 不可重复,不可以存储重复数据,如果存储的数据有重复值,只有保留一个元素。 我们经常利用“不可重复”特性对数据源进行“去重操作”。从代码角度去理解,Set集合由Set接口和Set接口的实现类组成。 #### Set接口常用的实现类有哪些? HashSet、TreeSet(Java独有,Kotlin没有)和MutableSet。 HashSet不保证Set的迭代顺序,不保证顺序恒久不变。允许使用null元素。 HashSet遍历集合的时候,按照自然顺序递增的排序,也可以制定比较器。 #### Set集合可以存储什么类型数据呢? Set集合可以存储各种类型数据。可以存储诸如Int、Double、String等基本数据类型,也可以存储其他自定义对象类型,比如自定义的学生Student。 ### Set集合3种创建方式 相比于List集合,**Set接口没有提供构造方法,所以,不能通过Set的构造方法创建Set集合**。 在Kotlin中创建Set集合有4种方式,通过Kotlin给我们封装好的3个方法创建或者直接调用TreeSet的构造方法。如下表格,我们将这四种方式列举了出来。 | **创建set集合方法** | **返回值类型** | **是否可写** | **是否有序** | | --------------------- | ---------- | -------- | -------- | | **setOf ()方法** | Set | 否 | 无 | | **hashSetOf ()方法** | HashSet | 是 | 无 | | **mutableSetOf ()方法** | MutableSet | 是 | 无 | | **TreeSet构造方法(Java独有的)** | TreeSet | 是 | 有 | 我们可以直接看3种方式的对应方法的方法签名,重点看返回值,方法体的部分我直接去掉了,对应的源码如下: ``` //方式一 public fun<T> setOf(vararg elements: T): Set<T>{} //方式二 public fun<T> hashSetOf(vararg elements: T): HashSet<T>{} //方式三 public fun<T> mutableSetOf(vararg elements: T): MutableSet<T>{} //方式四:必须要引入包:java.util.* public TreeSet(){} ``` 作为了解,可以通过翻看源码得知4种创建集合方式上的细微差别,有的是通过Java里面的HashSet创建,有的是通过toSet方法转换为的set集合。这个意义不是很大。 更重要的是需要知道,通过setOf方法创建的set集合是不可写的,通过hashSetOf方法、mutableSetOf方法、TreeSet构造方法创建的集合是可写的。具体使用的时候,忘记是否可写怎么办?只需要点进去看看方法的返回值即可,返回Set不可写,返回HashSet、MutableSet则可写。或者,看每个方法的注释,返回只读的set集合,会出现“**read-only** **set**”字样。比如,我们查看setOf方法的源码注释: ~~~ /** * Returns a new read-only set with the given elements. * Elements of the set are iterated in the order they were specified. * The returned set is serializable (JVM). * @sample samples.collections.Collections.Sets.readOnlySet */ public fun <T> setOf(vararg elements: T): Set<T> = if (elements.size > 0) elements.toSet() else emptySet() ~~~ 我们通过代码演示集合的4种创建方式: ~~~ fun main(args: Array<String>) { val set = setOf("a", "b", "c") println(set) val set1 = hashSetOf(1, 2, 3) println(set1) val set2 = mutableSetOf("a", "b", 3) println(set2) val set3 = TreeSet<String>() set3.add("a") set3.add("b") set3.add("c") println(set3) } ~~~ 运行结果 ``` [a, b, c] [1, 2, 3] [a, b, 3] [a, b, c] Process finished with exit code 0 ``` ### Set集合的可写性验证以及转换 我们通过代码验证集合是否可写,先验证集合可写,参考代码: ~~~ fun main(args: Array<String>) { val set = setOf("a", "b", "c") //set.add(4)//这里报错,说明不可写 println(set) val set1 = hashSetOf(1, 2, 3) set1.add(4)//可写 println(set1) val set2 = mutableSetOf("a", "b", 3) set2.add(4)//可写 println(set2) val set3 = TreeSet<String>() set3.add("a") set3.add("b") set3.add("c") set3.add("love") println(set3) } ~~~ 运行结果 ``` [a, b, c] [1, 2, 3, 4] [a, b, 3, 4] [a, b, c, love] Process finished with exit code 0 ``` 但是,不可写集合可以通过toMutableSet转换为可写集合,然后在进行写操作,参考代码: ~~~ fun main(args: Array<String>) { val set = setOf("a", "b", "c") //set.add(4)//这里报错,说明不可写 set.toMutableSet().add("truth")//不可写转可写 println(set)//为啥输出结果是[a, b, c],原因和上一节list的一样 } ~~~ 运行结果 ``` [a, b, c] Process finished with exit code 0 ``` ### Set集合数据不可重复 Set集合中的元素不可重复,是set相对于List的一个重要特点。同时,Set集合中可以存储null元素,我们通过一个案例验证下Set集合不可以包含重复元素,参考代码: ~~~ fun main(args: Array<String>) { //set集合数据不可重复,会自动去重 val set4 = setOf(1, 1, 2, 2, 5, null, null) println(set4) } ~~~ 运行结果 ``` [1, 2, 5, null] Process finished with exit code 0 ``` ### Set集合数据无序 我们常说Set集合无序,应该怎么去理解呢?就是我们的Set接口,不保证加入和取出顺序一致。但是子接口会做一些保证。 * HashSet内部存储的时候会根据元素的hashCode排序,元素取出的时候无序。 * LinkedHashSet内部存储的时候也会根据元素的hashCode排序,但是使用链表存储,元素取出顺序和插入顺序一致。 * TreeSet内部存储按照自然顺序对元素排序,但是对开发者不可见。 我们通过例子,演示下,Set集合的无序性: ~~~ fun main(args: Array<String>) { //内部使用HashSet无序 val set = hashSetOf("T桃子", "J橘子", "X杏子", "P苹果", "L梨", "S山楂", "G甘蔗", "M猕猴桃", "X香蕉") println("set:${set}") //内部使用LinkedHashSet,存储按照hashCode,输出按照输入顺序 val set1 = setOf("T桃子", "J橘子", "X杏子", "P苹果", "L梨", "S山楂", "G甘蔗", "M猕猴桃", "X香蕉") println("set1:${set1}") //内部使用LinkedHashSet,存储按照hashCode,输出按照输入顺序 val set2 = mutableSetOf("T桃子", "J橘子", "X杏子", "P苹果", "L梨", "S山楂", "G甘蔗", "M猕猴桃", "X香蕉") println("set2:${set2}") //TreeSet自然顺序 val set3 = TreeSet<String>() set3.add("T桃子") set3.add("J橘子") set3.add("X杏子") set3.add("P苹果") set3.add("L梨") set3.add("S山楂") set3.add("G甘蔗") set3.add("M猕猴桃") set3.add("X香蕉") println("set3:${set3}") } ~~~ 运行结果 ``` set:[P苹果, M猕猴桃, X杏子, S山楂, G甘蔗, J橘子, L梨, X香蕉, T桃子] set1:[T桃子, J橘子, X杏子, P苹果, L梨, S山楂, G甘蔗, M猕猴桃, X香蕉] set2:[T桃子, J橘子, X杏子, P苹果, L梨, S山楂, G甘蔗, M猕猴桃, X香蕉] set3:[G甘蔗, J橘子, L梨, M猕猴桃, P苹果, S山楂, T桃子, X杏子, X香蕉] Process finished with exit code 0 ``` 针对以上代码我们看到set是无序输出。set1和set2则按照插入顺序输出。set3按照自然顺序输出(最具实际开发意义)。 ### Set集合遍历 前面我们学习了区间、数组、List集合的遍历。那如何遍历Set集合呢?Set集合的遍历和List、数组的遍历一样。 也就是Set集合在遍历的时候,可以普通的for循环,还可以for循环的时候调用withIndex方法,参考代码: ~~~ fun main(args: Array<String>) { val set = setOf(1, 1, 2, 2, 3, 3, 4, 4, 5, 5) println("--------普通的for循环遍历---------") for (c in set) { println(c) } println("--------for循环遍历 withIndex---------") for (withIndex in set.withIndex()) { println("${withIndex.index}->${withIndex.value}") } println("--------for循环遍历 解析析构---------") for ((index, value) in set.withIndex()) { println("${index}->${value}") } } ~~~ 运行结果 ``` --------普通的for循环遍历--------- 1 2 3 4 5 --------for循环遍历 withIndex--------- 0->1 1->2 2->3 3->4 4->5 --------for循环遍历 解析析构--------- 0->1 1->2 2->3 3->4 4->5 Process finished with exit code 0 ``` 当然,还可以通过高阶函数进行遍历操作,这个我们后续再去讲解。