集合-JAVA

一、常用的集合类

  • Map接口和Collection接口是所有集合框架的父接口
    • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
    • Collection接口的子接口包括:Set接口、List接口和Queue接口
      • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
      • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

1、List,Set,Map三者的区别

在这里插入图片描述

1.1 Collection
  • Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List
1.1.1 List

一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector

1.1.1 Set

一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及TreeSet

1.2 Map
  • Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象
1.1.1 Map

Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

2、集合框架底层数据结构

2.1 Collection
  • List
    1. Arraylist: Object数组
    2. Vector: Object数组
    3. LinkedList: 双向循环链表
  • Set
    1. HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
    2. LinkedHashSet: LinkedHashSet 继承于HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于HashSet 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的
    3. TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
2.2 Map
  1. HashMap
    • JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)
    • JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
  2. LinkedHashMap
    LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑
  3. HashTable
    数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 —线程安全
  4. TreeMap:
    红黑树(自平衡的排序二叉树)

3、线程安全的集合类

  1. Vector
    比Arraylist多了个 synchronized (线程安全),因为效率较低,现在已经不太建议使
  2. hashTable
    比hashMap多了个synchronized (线程安全),不建议使用
  3. ConcurrentHashMap
  • 是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成
  • Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据
  • 一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁

4、Java集合的快速失败机制 “fail-fast“

  • java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast 机制

  • 假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制

  • 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集
    合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历

  • 解决办法

    1. 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized
    2. 使用CopyOnWriteArrayList来替换ArrayLis

5、List接口

5.1、迭代器 Iterator
  • Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素
  • 所有Collection接继承了Iterator迭代器
1.2 Iterator 使用
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}

Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常

1.3 遍历边移除 Collection 中的元素
  • 边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*  移除元素
it.remove();
}
  • foreach(for(Integer i : list))
for(Integer i : list){
list.remove(i)
}
  • foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它,运行以上错误代码会报 ConcurrentModificationException 异常
1.4 Iterator 和 ListIterator 区别
  1. Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List
  2. Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)
  3. ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置
1.4 遍历一个 List 不同的方式
  1. . for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止
  2. 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式
  3. foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换
5.2 ArrayList
  1. ArrayList的优点
    • ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快
    • ArrayList 在顺序添加一个元素的时候非常方便
  2. ArrayList 的缺点
    • 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能
    • 插入元素的时候,也需要做一次元素复制操作,缺点同上
  3. 总结
  • ArrayList 比较适合顺序添加、随机访问的场景
5.2.1 ArrayList 和 LinkedList 的区别
  1. 数据结构
    ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现
  2. 随机访问效率
    ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找
  3. 增加和删除效率
    在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据元素的下标
  4. 内存空间占用
    LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素
  5. 线程安全
    ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
  6. LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
  7. 总结
    在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList
5.2.1 ArrayList 和 Vector 的区别
  1. 这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合
    1. 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的
    2. 性能:ArrayList 在性能方面要优于 Vector
    3. 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%
  2. Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间
  3. Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist
5.3 链表
  1. 链表是可以将物理地址上不连续的数据连接起来,通过指针来对物理地址进行操作,实现增删改查等功能
  2. 链表大致分为单链表和双向链表
    • 单链表:每个节点包含两部分,一部分存放数据变量的data,另一部分是指向下一节点的next指针
      在这里插入图片描述
    • 双向链表:除了包含单链表的部分,还增加的pre前一个节点的指针
      在这里插入图片描述
  3. 链表的优点
    • 插入删除速度快(因为有next指针指向其下一个节点,通过改变指针的指向可以方便的增加删除元素)
    • 内存利用率高,不会浪费内存(可以使用内存中细小的不连续空间(大于node节点的大小),并且在需要空间的时候才创建空间)
    • 大小没有固定,拓展很灵活
  4. 链表的缺点
    • 不能随机查找,必须从第一个开始遍历,查找效率低

6、Set接口

6.1 HashSet 实现原理

HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层HashMap 的相关方法来完成,HashSet 不允许重复的值

6.2 HashSet检查重复
  • 向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较
  • HashSet 中的add ()方法会使用HashMap 的put()方法
  • HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复(HashMap 比较key是否相等是先比较hashcode 再比较equals )
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
return map.put(e, PRESENT)==null;
}
6.3 hashCode()与equals()的相关规定
  1. 如果两个对象相等,则hashcode一定也是相同
    • hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
  2. 两个对象相等,对两个equals方法返回true
  3. 两个对象有相同的hashcode值,它们也不一定是相等的
  4. equals方法被覆盖过,则hashCode方法也必须被覆盖
  5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
  6. ==与equals的区别
    • ==是判断两个变量或实例是不是指向同一个内存空间 ,equals是判断两个变量或实例所指向的内存空间的值是不是相同
    • ==是指对内存地址进行比较 equals()是对字符串的内容进行比较

7、 Map接口

7.1 Hash算法

哈希算法是指把任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值叫做哈希值

7.2 HashMap实现原理
  1. HashMap概述
    HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变
  2. HashMap的数据结构
    在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外,HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体
  3. HashMap 基于 Hash 算法实现
    • 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
    • 存储元素
      如果出现hash值相同的key,此时有两种情况
      • 如果key相同,则覆盖原始值
      • 如果key不同(出现冲突),则将当前的key-value放入链表中,Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
    • 获取数据元素
      直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值
7.3 HashMap在JDK1.7和JDK1.8中底层实现不同
  • 在Java中,保存数据有两种比较简单的数据结构:数组和链表
  • 数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易
  • 我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突
  1. HashMap JDK1.8之前
    • JDK1.8之前采用的是拉链法
      将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可
      在这里插入图片描述
  2. HashMap JDK1.8之后
    • 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
      在这里插入图片描述
  3. JDK1.7 VS JDK1.8 比较
  • JDK1.8主要解决或优化了一下问题
    • resize 扩容优化
    • 引入了红黑树,目的是避免单条链表过长而影响查询效率,
    • 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题
不同1.71.8
存储结构数组 + 链表数组 + 链表 + 红黑树
初始化方式单独函数: inflateTable()直接集成到了扩容函数 resize() 中
hash值计算方式扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
存放数据的规则无冲突时,存放数组;冲突时,存放链表无冲突时,存放数组;冲突 & 链表长度 <8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
插入数据方式头插法(先将原位置的数据移到后1位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)
扩容后存储位置的计算方式全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1))按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)
7.4 HashMap的put方法的具体流程
  1. 当我们put的时候,首先计算 key 的 hash 值,这里调用了 hash 方法, hash 方法实际是让key.hashCode() 与 key.hashCode()>>>16 进行异或操作,高16bit补0,一个数和0异或不变
  2. 以 hash 函数作作用
    高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞
  3. 因为bucket数组大小是2的幂,计算下标 index = (table.length - 1) &hash ,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位
  4. JDK8中用了复杂度 O(logn)的树结构来提升碰撞下的性能。
  5. putVal方法执行流程图
    在这里插入图片描述
7.5 HashMap的扩容操作
  1. 在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容
  2. 每次扩展的时候,都是扩展2倍
  3. 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置
  4. 在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方
  5. 在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置对其进行分发,但在1.8版本中,则是根据要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
7.6 解决哈希冲突
  • 链表法
    将相同hash值的对象组织成一个链表放在hash值对应的槽位

  • 开放地址法
    通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位

  • 相比于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4 (即2的四次方16)要远小于int类型的范围,让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己
右移16位进行异或运算(高低位异或)
}
  • 相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动)
7.7 不能直接使用hashCode()处理后的哈希值直接作为table的下标

hashCode() 方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过 hashCode() 计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置

  • 解决方案
    • HashMap自己实现了自己的 hash() 方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均
    • 在保证数组长度为2的幂次方的时候,使用 hash() 运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题
7.8 HashMap 与 HashTable 区别
  1. 线程安全
    HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap )
  2. 效率
    因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;(如果你要保证线程安全的话就使用 ConcurrentHashMap )
  3. 对Null key 和Null value的支持
    HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException
  4. 初始容量大小和每次扩充容量大小的不同
    • 创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍
    • . 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小
  5. 底层数据结构
    JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制
7.9 TreeMap
  1. TreeMap 是一个有序的key-value集合,它是通过红黑树实现的
  2. TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法
  3. TreeMap是线程非同步的
7.10 HashMap TreeMap 区别
  • 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择
  • 对一个有序的key集合进行遍历,TreeMap是更好的选择

8、ConcurrentHashMap 底层实现

  1. JDK1.7的实现

    • 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问
    • 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现
    • 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁
      在这里插入图片描述
    • 包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色
    • Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁
  2. JDK1.8的实现

    • 在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍
      在这里插入图片描述
    • 如果相应位置的Node还没有初始化,则调用CAS插入相应的数据
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
  • 如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,如果该节点的hash不小于0,则遍历链表更新节点或插入新节点
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
  • 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;
  • 如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount
8.1 comparable 和 comparator的区别
  • comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
  • comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
  • 一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort()
8.2 Collection 和 Collections 区别
  • Collection
    java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set
  • Collections
    Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值