集合框架面试题

文章目录

  • ArrayList 和 Vector 的区别
    • 1. 同步性
    • 2. 数据增长
    • 补充说明
  • ArrayList, Vector, LinkedList 的存储性能和特性
    • 性能对比和选择建议
    • 结论
  • 快速失败 (Fail-Fast) 和安全失败 (Fail-Safe) 的区别
    • 区别总结
  • HashMap 的数据结构
  • HashMap 的工作原理
  • HashMap 扩容机制
  • List、Map、Set 存取元素特点比较
    • List
    • Set
    • Map
    • 补充说明
  • Set 中元素的重复判断
    • 区别总结
    • 两个对象值相同但 hash code 不同的情况
  • 堆(Heap)和栈(Stack)的区别
    • 栈(Stack)
    • 堆(Heap)
    • 区别总结
  • Java集合类框架的基本接口
    • 1. Collection接口
    • 2. Set接口
    • 3. List接口
    • 4. Map接口
  • HashSet 和 TreeSet 的区别
    • 总结:
  • HashSet的底层实现
    • 实现原理:
    • 总结:
  • LinkedHashMap的实现原理
    • 实现机制:
    • 总结:
  • 集合类没有实现Cloneable和Serializable接口的原因如下
  • 什么是迭代器 (Iterator)?
  • Iterator 和 ListIterator 的区别
  • 数组(Array)和列表(ArrayList)的区别**
  • Java集合类框架的最佳实践包括:
  • Comparable 和 Comparator 接口
  • Collection 和 Collections 的区别


ArrayList 和 Vector 的区别

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),它们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组。我们可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是 HashSet 之类的集合的最大不同处。HashSet 之类的集合不允许有重复的元素,且不支持按索引号检索元素。

接下来,我们讨论 ArrayList 和 Vector 的区别,主要包括两个方面:

1. 同步性

  • Vector 是线程安全的,也就是说,它的方法之间是线程同步的。多个线程可以安全地访问 Vector 的方法,而无需我们自己编写线程安全的代码。

  • ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那么最好使用 ArrayList,因为它不考虑线程安全,效率会更高些。但如果有多个线程会访问集合,则最好使用 Vector,因为它已经内置了线程安全性。

2. 数据增长

  • 当存储进 ArrayList 和 Vector 的元素个数超过了容量时,它们需要增加存储空间。但它们的增长策略略有不同。

  • Vector 默认增长为原来的两倍,这意味着每当容量不足时,Vector 将分配原来容量大小的两倍空间。

  • ArrayList 的增长策略在文档中没有明确规定,但从源代码可以看到,ArrayList 的增长策略是增长为原来的 1.5 倍。

补充说明

  • Vector 和 Hashtable 是旧的,自 Java 1.0 起就提供了,它们是线程安全的。

  • ArrayList 和 HashMap 是 Java 2 时才提供的,它们是线程不安全的。

  • 可以在实际使用中根据需求选择适当的集合类。如果需要线程安全性,则可以使用 Vector;如果不需要线程安全性,则可以选择 ArrayList。

ArrayList, Vector, LinkedList 的存储性能和特性

  • ArrayList 和 Vector:

    • 使用数组方式存储数据,数组元素数大于实际存储的数据以便增加和插入元素。
    • 允许直接按序号索引元素,但插入元素涉及数组元素移动等内存操作,导致插入数据慢。
    • 索引数据快,插入数据相对较慢。
    • Vector 使用了 synchronized 方法(线程安全),因此在多线程环境下是线程安全的,但性能通常较 ArrayList 差。
  • LinkedList:

    • 使用双向链表实现存储。
    • 按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录本项的前后项即可,因此插入速度较快。
    • 在查找时速度较慢,但在插入与删除时具有优势。

性能对比和选择建议

  • ArrayList vs. Vector:

    • 如果不需要线程安全性,优先选择 ArrayList,因为它不需要额外的同步操作,性能较高。
    • 只有在多线程环境下才考虑使用 Vector,以保证线程安全性,但性能可能较差。
  • ArrayList vs. LinkedList:

    • 如果需要频繁地进行随机访问或查找操作,ArrayList 是更好的选择,因为它的索引速度更快。
    • 如果需要频繁地进行插入、删除操作,尤其是在集合的中间位置,LinkedList 可能更适合,因为它的插入和删除操作速度更快。

结论

  • 根据具体的需求和场景选择合适的数据结构,权衡各自的优劣势。
  • ArrayList 适合于需要快速访问元素的场景,而 LinkedList 适合于需要频繁插入、删除元素的场景。
  • 在多线程环境下,如果需要线程安全性,可以考虑使用 Vector,但要注意性能损耗。

快速失败 (Fail-Fast) 和安全失败 (Fail-Safe) 的区别

  • 快速失败 (Fail-Fast):

    • 在 Java 集合类中,所有位于 java.util 包下的集合类都属于快速失败。
    • 当多个线程对集合进行结构上的改变(例如增加或删除元素)时,迭代器会立即抛出 ConcurrentModificationException 异常,防止出现并发修改异常。
    • 快速失败机制主要是为了检测并发修改,保证在迭代过程中集合不被修改,从而避免出现数据不一致或不可预料的行为。
  • 安全失败 (Fail-Safe):

    • java.util.concurrent 包下的集合类都属于安全失败。
    • 安全失败机制通过在迭代器上进行修改检查或对底层数据结构做拷贝等方式,使得在迭代过程中对集合的修改不会导致异常抛出。
    • 即使在迭代过程中,其他线程对集合进行了修改,安全失败的迭代器也能正常遍历,并不会抛出异常。

区别总结

  • 快速失败:

    • 迭代器会立即检测到并发修改,抛出异常。
    • 可以确保集合的一致性和可靠性,但可能会影响程序的正常运行。
    • 主要用于单线程环境或在多线程环境下对并发修改行为进行检测和防范。
  • 安全失败:

    • 迭代器能够安全地遍历集合,即使在迭代过程中集合被修改也不会抛出异常。
    • 虽然不能保证集合的实时一致性,但能够确保迭代器的稳定性,避免了抛出异常可能导致的程序中断。
    • 适用于需要保障迭代器稳定性和对实时一致性要求不高的场景,尤其是在多线程环境下。

HashMap 的数据结构

在 Java 中,HashMap 实际上是一个数组和链表(或红黑树)的结合体,通常被称为“链表散列”。

  • 数组

    • 用于存储哈希桶,每个桶存储一个链表或红黑树的头节点。
    • 数组的长度通常称为容量(capacity),在创建 HashMap 时确定,一般为 2 的幂次方,如 16、32 等。
    • 每个桶对应一个哈希值范围,通过哈希函数将键的哈希值映射到对应的桶上。
  • 链表(或红黑树)

    • 用于解决哈希冲突,即多个键映射到相同的桶的情况。
    • 当多个键映射到同一个桶时,它们会形成一个链表或红黑树结构,存储在该桶内。
    • 如果链表长度过长(通常为 8),链表会被转换为红黑树,以提高查找效率,这种结构称为“链表散列+红黑树”。
  • 哈希函数

    • 用于将键的哈希值映射到桶的索引位置上,以实现快速的查找、插入和删除操作。
    • 好的哈希函数应当尽量减少哈希碰撞,即将不同的键映射到不同的桶上,以保证高效的哈希表性能。
      在这里插入图片描述

HashMap 的工作原理

  1. 存储结构

    • HashMap 是以键值对 (key-value) 的形式存储元素的。
    • 内部使用数组和链表(或红黑树)的结合体来实现存储。
  2. 哈希函数

    • HashMap 需要一个哈希函数,它使用 hashCode()equals() 方法来确定键值对的存储位置。
    • 当调用 put(key, value) 方法时,HashMap 会计算 key 的哈希值,然后将键值对存储在数组中适当的位置上。
  3. 处理哈希冲突

    • 当两个不同的键通过哈希函数映射到相同的索引位置时,会发生哈希冲突。
    • HashMap 使用链表或红黑树来解决哈希冲突,将具有相同哈希值的键值对存储在同一个索引位置的链表或树中。
  4. 容量和负载因子

    • HashMap 有一个初始容量和负载因子的概念。
    • 初始容量指的是 HashMap 内部数组的大小,通常是 16。
    • 负载因子是一个表示数组何时需要扩容的比例,默认值是 0.75。
    • 当哈希表中存储的键值对数量达到容量乘以负载因子时,会触发数组的扩容操作。
  5. 扩容

    • 当 HashMap 中的元素个数达到负载因子限制时,会触发数组的扩容操作。
    • 扩容操作会将数组大小增加一倍,并重新计算每个元素的存储位置。
  6. 查找和插入复杂度

    • 在理想情况下,HashMap 的查找和插入操作的时间复杂度为 O(1)。
    • 但在发生哈希冲突的情况下,查找和插入操作的时间复杂度可能会增加,最坏情况下为 O(n)。

HashMap 扩容机制

当 HashMap 中的元素个数超过数组大小的负载因子(loadFactor)时,就会触发数组扩容的操作。

  • 负载因子(Load Factor):负载因子是指 HashMap 中元素数量与数组容量的比值。默认情况下,负载因子的值为 0.75。

  • 数组扩容时机

    • 当 HashMap 中元素个数超过了负载因子与当前数组容量的乘积时,即超过了扩容阈值(threshold),就会触发数组扩容。
    • 扩容阈值 = 负载因子 * 当前数组容量。
  • 扩容操作

    • 扩容时,HashMap 会创建一个新的数组,其大小通常为原数组的两倍。
    • 然后,HashMap 会将原数组中的元素重新计算 hash,并放入新的数组中。
    • 这个过程涉及到重新计算每个元素在新数组中的位置,因此是一个比较耗时的操作。
  • 优化建议

    • 如果能够预估 HashMap 中的元素数量,可以通过构造函数初始化时指定初始容量,从而避免多次扩容操作。
    • 在构造 HashMap 时,可以通过设置初始容量为预期大小的两倍来减少扩容次数。
    • 例如,如果预计 HashMap 中将存储 1000 个元素,则初始化时使用 new HashMap<>(2048) 会更合适,即将初始容量设置为预期大小的两倍。

List、Map、Set 存取元素特点比较

List

  • 特点:
    • 有先后顺序,允许重复元素。
    • 使用索引来存取元素,可以根据索引获取特定位置的元素。
    • 允许插队操作,可以在指定位置插入元素。

Set

  • 特点:
    • 不允许重复元素,确保集合中没有相同对象。
    • 没有明确的顺序,无法按照索引访问元素。
    • 使用 Iterator 接口进行遍历,逐一访问元素。

Map

  • 特点:
    • 存储键值对,每个键映射到一个值。
    • 键是唯一的,值可以重复。
    • 可以通过键来获取对应的值,键是唯一的标识符。

补充说明

  • List与Set:

    • List和Set都是单列元素的集合,有一个共同的父接口叫做Collection。
    • Set不允许有重复的元素,重复指的是对象相等而不是仅仅相同。
    • Set集合的add方法返回一个boolean值,如果集合中没有某个元素并成功加入,则返回true;如果集合中已经存在与某个元素相等的元素,则add方法无法加入,返回false。
  • Map:

    • Map是双列的集合,通过键值对存储元素。
    • put方法用于存储键值对,每次存储时需要一个键和对应的值,键是唯一的。
    • 通过键可以获取对应的值,使用get(Object key)方法。
    • 可以获取所有键的集合、所有值的集合,以及键值对的集合。

Set 中元素的重复判断

在 Set 中,元素的重复与否是通过调用元素的 equals() 方法进行判断的,而不是通过 == 运算符。

  • equals() 方法

    • equals() 方法是 Java 中用于比较两个对象是否相等的方法。
    • 在 Set 中,当向集合中添加新元素时,会先调用该元素的 equals() 方法来检查是否已经存在相等的元素。
    • 如果该方法返回 true,表示集合中已经存在相等的元素,新元素不会被添加;如果返回 false,则新元素会被添加到集合中。
  • == 运算符

    • == 运算符用于比较两个对象的引用是否指向同一个内存地址。
    • 在 Java 中,== 运算符通常用于比较基本数据类型的值或对象的引用,而不是用于比较对象的内容是否相同。
    • 即使两个对象的内容相同,但它们指向的内存地址不同,== 运算符也会返回 false

区别总结

  • equals() 方法用于比较两个对象的内容是否相同,是通过类中的重写来实现的,可以根据具体的业务需求来自定义判断逻辑。
  • == 运算符用于比较两个对象的引用是否指向同一个内存地址,即判断对象是否为同一个实例。

两个对象值相同但 hash code 不同的情况

对。在 Java 中,两个对象如果通过 equals() 方法比较返回 true,则它们被认为是相等的。但是,它们的 hash code 可能会不同。

  • 情况说明:

    • 如果两个对象要存储在 HashSet 或 HashMap 等基于哈希的集合中,它们的 equals() 方法返回 true 时,它们的 hash code 值必须相等。
    • 因为这些集合在存储对象时,会先根据 hash code 的值确定存储位置,然后再使用 equals() 方法进行精确比较。
    • 如果两个对象的 hash code 不相等,即使它们的内容相同,它们仍然被认为是不同的对象,这可能导致在集合中存储同一内容的多个副本。
  • 其他情况:

    • 如果对象不需要存储在哈希集合中,则它们的 hash code 是否相等并不重要。
    • 例如,对于 ArrayList 这样的集合,其存储元素的顺序是按照添加顺序进行的,而不是根据 hash code,因此 hash code 的值对于集合的存储顺序并不产生影响。
    • 虽然没有必要,但通常情况下,我们仍然会去实现 hashCode() 方法,以遵循对象的通用约定。

堆(Heap)和栈(Stack)的区别

栈(Stack)

  • 作用

    • 栈内存用于存放方法调用的局部变量、方法参数、返回值以及方法的执行环境。
    • 每个线程在运行时都会有自己的栈空间,用于存放线程执行过程中的数据。
  • 特点

    • 栈内存的大小是固定的,在程序运行时就已经确定了。
    • 栈内存的生命周期与方法的调用过程相关,方法执行结束后,栈帧会被弹出,相应的内存空间也会被释放。
  • 存储内容

    • 存放局部变量、方法参数、返回值等方法相关的数据。
    • 存放基本数据类型的变量和对象引用。

堆(Heap)

  • 作用

    • 堆内存用于存放动态分配的对象,包括所有的 Java 对象实例和数组。
    • 所有通过 new 关键字创建的对象都存放在堆内存中。
  • 特点

    • 堆内存的大小可以动态调整,取决于 JVM 的堆内存设置以及当前系统资源的可用情况。
    • 堆内存中的对象生命周期不受方法的影响,对象的生存周期由程序员控制,直到没有任何引用指向对象时,对象才会被垃圾回收器回收。
  • 存储内容

    • 存放所有的 Java 对象实例和数组。
    • 存放通过 new 关键字动态创建的对象。

区别总结

  • 生命周期

    • 栈内存的生命周期与方法调用相关,方法执行结束后栈帧被弹出;
    • 堆内存中的对象生命周期由程序员控制,直到没有引用指向对象时,对象才会被回收。
  • 大小和管理

    • 栈内存大小固定,由 JVM 在启动时分配;
    • 堆内存大小动态调整,取决于 JVM 的设置和系统资源。
  • 存储内容

    • 栈存放局部变量、方法参数、返回值等方法相关的数据;
    • 堆存放所有的 Java 对象实例和数组。

Java集合类框架的基本接口

Java集合类框架提供了一组接口,用于操作和管理一组对象。其中,最基本的接口包括:

1. Collection接口

  • 代表一组对象,每个对象都是它的子元素。
  • 不同的集合类实现了Collection接口来提供不同的存储和操作方式。

2. Set接口

  • 是Collection的子接口。
  • 包含不重复元素的集合。
  • 不能有重复的元素。

3. List接口

  • 也是Collection的子接口。
  • 有顺序的集合,可以包含重复元素。
  • 允许根据索引访问元素。

4. Map接口

  • 不是Collection的子接口。
  • 将键映射到值的对象,每个键最多只能映射到一个值。
  • 键是唯一的,值可以重复。

HashSet 和 TreeSet 的区别

特点 HashSet TreeSet
实现机制 使用哈希表(hash table)实现,基于 HashMap 实现。 使用树结构(红黑树)实现,基于 TreeMap 实现。
元素顺序 无序集合,元素存储顺序不固定,取决于哈希表的散列算法。 有序集合,元素按照自然顺序或指定的比较器进行排序。
性能 插入、删除、查找元素的时间复杂度为 O(1),基于哈希函数的快速查找。 插入、删除、查找元素的时间复杂度为 O(log n),基于树结构的二分查找。
重复元素 不允许存储重复元素,会自动去重。 不允许存储重复元素,会自动去重。
遍历顺序 遍历顺序不确定,取决于哈希表的散列算法。 遍历顺序是有序的,按照元素的自然顺序或者指定的比较器顺序。
存储性能 在添加和查找元素时性能较高,适合频繁的插入、删除和查找操作。 在添加和查找元素时性能较慢,适合需要保持有序性的场景。

总结:

  • HashSet

    • 无序集合,基于哈希表实现,插入、删除、查找元素的时间复杂度为 O(1)。
    • 不允许存储重复元素,适合频繁的插入、删除和查找操作,但遍历顺序不确定。
  • TreeSet

    • 有序集合,基于树结构实现,插入、删除、查找元素的时间复杂度为 O(log n)。
    • 不允许存储重复元素,适合需要保持有序性的场景,但在添加和查找元素时性能较 HashSet 较慢。

HashSet的底层实现

HashSet的底层实现是依赖于HashMap的。HashSet内部维护了一个HashMap对象作为存储容器,其中HashSet中的元素实际上是作为HashMap的key存在的。在HashSet的构造方法中会初始化一个HashMap对象,而HashSet中的元素则被存储在HashMap中的key位置上。

实现原理:

  1. 当我们向HashSet中添加元素时,HashSet会调用HashMap的put方法将该元素作为HashMap的key存储进去,而对应的value则是一个占位对象。

  2. 在HashMap中,key是唯一的,因此HashSet中不允许存在重复元素。当我们尝试向HashSet中添加一个已经存在的元素时,HashMap的put方法会返回旧值,并且新值不会被添加进HashSet,这样就保证了HashSet中元素的唯一性。

  3. HashSet对外提供的方法,例如add、remove、contains等操作,实际上是通过调用HashMap相应的方法来实现的,因为HashSet内部维护的是一个HashMap对象。

总结:

  • HashSet的底层实现是基于HashMap的,通过HashMap的键值对机制来保证HashSet中元素的唯一性。

  • HashSet对外提供的操作方法都是通过调用HashMap的相应方法来实现的,因此HashSet的操作效率取决于HashMap的性能。

LinkedHashMap的实现原理

LinkedHashMap是基于HashMap实现的,它在HashMap的基础上增加了对插入顺序和访问顺序的支持。它通过维护一个双向链表来实现按插入顺序或访问顺序排序。

实现机制:

  1. LinkedHashMap内部维护了一个特殊的链表结构,用来保存键值对的顺序。链表中的每个节点都是HashMap的Entry,但是多了before和after两个属性,以及一个header节点作为头结点。

  2. 当新的元素插入到LinkedHashMap中时,会将该元素作为一个新节点插入到链表的尾部,并更新header节点。如果该元素已经存在于LinkedHashMap中,则不会改变其位置。

  3. 对于访问顺序排序,当访问某个已存在的元素时,会将该元素对应的节点移动到链表的尾部,表示最近被访问。这样,最近访问的元素会排在链表的尾部,而最不经常访问的元素会排在链表的头部。

  4. LinkedHashMap还有一个accessOrder属性,用来控制是否开启访问顺序排序。默认情况下,accessOrder为false,表示采用插入顺序排序;如果将accessOrder设置为true,则会开启访问顺序排序。

总结:

  • LinkedHashMap是基于HashMap实现的,但是在HashMap的基础上增加了对插入顺序和访问顺序的支持。

  • LinkedHashMap内部通过维护一个双向链表来实现对元素的排序,插入新元素时会将其添加到链表的尾部,访问已存在的元素时会将其移动到链表的尾部。

  • LinkedHashMap的排序方式可以通过accessOrder属性进行控制,默认为插入顺序排序。

集合类没有实现Cloneable和Serializable接口的原因如下

  1. 语义的不确定性:克隆和序列化的语义与具体的实现相关,不同的集合类可能有不同的实现方式,因此,集合类的克隆或序列化行为不确定,无法在接口级别统一定义。

  2. 灵活性和可控性:克隆和序列化是一种比较底层的操作,涉及到对象的内部结构和状态。如果集合类强制实现Cloneable和Serializable接口,就会限制了实现的灵活性和可控性,因为这些接口可能并不符合某些特定集合类的设计需求。

  3. 接口与实现的分离:Java的集合类框架遵循接口与实现分离的原则,即接口定义了集合的行为,具体的实现类负责实现这些行为。Cloneable和Serializable接口并不是集合类框架的核心行为,因此不需要强制所有集合类都实现这些接口。

**迭代器(Iterator)**是Java集合框架中的一个接口,用于遍历集合中的元素。它提供了一种统一的方式来访问集合中的元素,而不需要了解集合的内部实现细节。

什么是迭代器 (Iterator)?

迭代器主要用于遍历集合,并且允许在遍历过程中删除集合中的元素。每个集合类都实现了iterator()方法,用于返回一个对应的迭代器实例。

迭代器的常用方法包括:

  • hasNext():判断是否还有下一个元素可供迭代。
  • next():返回下一个元素,并将迭代器指针向后移动一个位置。
  • remove():从集合中移除迭代器返回的最后一个元素。

迭代器的使用方式通常是通过循环来遍历集合,例如:

List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Orange");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    System.out.println(fruit);
}

Iterator 和 ListIterator 的区别

  1. 适用范围

    • Iterator接口适用于遍历Set和List集合。
    • ListIterator接口只能用于遍历List集合。
  2. 遍历方向

    • Iterator只能以单向的方式(前向)遍历集合,即从前往后遍历。
    • ListIterator可以双向遍历List集合,即既可以从前向后遍历,也可以从后向前遍历。
  3. 功能扩展

    • ListIterator在Iterator接口的基础上添加了一些额外的功能,例如:添加元素、替换元素、获取前一个和后一个元素的索引等。

数组(Array)和列表(ArrayList)的区别**

  1. 数据类型

    • 数组(Array)可以包含基本数据类型(如int、double等)和对象类型(如String、Object等)。
    • ArrayList只能包含对象类型,不能直接存储基本数据类型,但可以使用对应的包装类(如Integer、Double等)进行存储。
  2. 大小

    • 数组的大小是固定的,一旦创建后大小不可改变。
    • ArrayList的大小是动态变化的,它会根据需要自动扩容或缩减。
  3. 性能

    • 在处理固定大小的基本数据类型时,使用数组的性能通常会更好,因为它避免了自动装箱和拆箱的开销,而ArrayList需要将基本数据类型转换为对应的包装类对象,这会带来一定的性能损失。

何时应该使用Array而不是ArrayList?

  • 当需要存储的元素个数是固定且已知的,并且希望在性能上获得一些优势时,可以使用数组。
  • 当需要存储基本数据类型的固定大小集合时,数组是更好的选择,因为它不涉及自动装箱和拆箱的开销。
  • 当需要高效的随机访问元素时,数组通常比ArrayList更快,因为数组的元素可以直接通过索引进行访问,而ArrayList需要进行一次额外的方法调用。

Java集合类框架的最佳实践包括:

  1. 根据需求选择合适的集合类型:根据需求和场景选择合适的集合类型,例如,如果元素大小固定且能事先知道,可以选择数组;如果需要动态变化大小的集合,可以选择ArrayList或LinkedList等。

  2. 设置初始容量:对于支持设置初始容量的集合类,如果能够估计存储的元素数量,应该设置初始容量以避免重新计算hash值或扩容。

  3. 使用泛型:为了类型安全、可读性和健壮性,始终使用泛型。使用泛型还可以避免在运行时出现ClassCastException。

  4. 使用不可变类作为Map的键:使用JDK提供的不可变类作为Map的键可以避免为自定义类实现hashCode()和equals()方法。

  5. 接口优于实现:在编程时,应该尽量依赖接口而不是具体的实现类,这样可以提高代码的灵活性和可维护性。

  6. 空值处理:当底层集合实际上为空时,应该返回长度为0的集合或数组,而不是返回null,以避免空指针异常。

Comparable 和 Comparator 接口

Comparable 接口

  • Comparable 接口包含一个 compareTo() 方法,用于对类的对象进行自然排序。
  • compareTo() 方法返回负数、0、正数分别表示当前对象小于、等于、大于指定对象。
  • 实现 Comparable 接口的类可以直接使用Java提供的排序算法,如Arrays.sort()和Collections.sort()。

Comparator 接口

  • Comparator 接口包含两个方法:compare() 和 equals()。
  • compare() 方法用于对两个对象进行比较并返回排序顺序,返回负数、0、正数表示第一个对象小于、等于、大于第二个对象。
  • equals() 方法用于判断两个 Comparator 对象是否相等。
  • Comparator 接口可以用于实现自定义的比较规则,对不可修改的类或者不适合实现 Comparable 接口的类进行排序。

区别

  • Comparable 接口是自然排序的一部分,直接影响对象的排序方式,而Comparator 接口则是在需要时提供的一种外部比较器。
  • Comparable 接口通常用于对类的对象进行自然排序,而Comparator 接口则允许在不改变类定义的情况下定义多种排序规则。

Collection 和 Collections 的区别

Collection 接口

  • Collection 接口是Java集合框架的根接口,它是所有集合类的父接口。
  • Collection 接口定义了集合类的基本操作,如添加、删除、遍历等。

Collections 类

  • Collections 类是Java集合框架提供的一个工具类,包含了一系列静态方法,用于对集合进行操作。
  • Collections 类提供了对集合的排序、搜索、线程安全等操作。

区别

  • Collection 接口是所有集合类的父接口,定义了集合类的基本操作。
  • Collections 类是一个工具类,包含了一系列静态方法,用于对集合进行操作,如排序、搜索等。
上一篇:【RAG实践】基于 LlamaIndex 和Qwen1.5搭建基于本地知识库的问答机器人


下一篇:Iterator对象功能学习