先来看看集合的继承关系图,如下图所示:
其中:
- 外框为虚线表示接口,边框为实线表示类;
- 箭头为虚线的表示实现了界面,箭头为实线的表示继承了类别。
为了便于理解,我隐藏了一些与本文内容无关的信息,这些隐藏的内容将在下一章中详细介绍。
从图中可以看出,集合的根节点是 Collection,而 Collection 下面还提供了两个常见的集合,分别是:
- List:使用最多的有序集合,提供方便的新增、修改、删除操作;
- Set:不允许在许多需要保证元素唯一性的场景中使用重复元素。
下面我们详细介绍一下集合类。
集合使用1)VectorVector 是 Java 如果不需要线程安全,则不建议使用早期提供的线程安全有序集合。毕竟,同步是有线程费用的。
使用示例代码:
Vector vector = new Vector();vector.add("dog");vector.add("cat");vector.remove("cat");System.out.println(vector);
程序执行结果:[dog]
ArrayList 它是最常见的非线程安全有序集合。由于内部存储在数组中,随机访问效率很高,但非尾部插入和删除性能较低。如果将元素插入中间,则所有元素将向后移动。ArrayList 的使用与 Vector 类似。
3)LinkedListLinkedList 采用双向链表数据结构,增删效率较高,随机访问效率较差。
LinkedList 除上述两种操作方法外,还增加了几种操作方法,如 offer() 、peek() 等,详情请参考以下代码:
LinkedList linkedList = new LinkedList();// 添加元素linkedlisttt添加元素.offer("bird");linkedList.push("cat");linkedList.push("dog");// System获得第一个元素.out.println(linkedList.peek());// 获取第一个元素,删除此元素System.out.println(linkedList.poll());System.out.println(linkedList);
程序执行结果:
dogdog[cat, bird]
4)HashSetHashSet 它是一个没有重复元素的集合。虽然是 Set 集合的子类实际上是 HashMap 相关源代码如下:
public HashSet() { map = new HashMap<>();}
因此 HashSet 无序集合,无法保证元素的顺序。
HashSet 默认容量为 16,每次扩充 0.75 倍,相关源代码如下:
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c);}
HashSet 的使用与 Vector 类似。
5)TreeSetTreeSet 集合实现了自动排序,即集合 TreeSet 将您插入数据进行自动排序。
示例代码如下:
TreeSet treeSet = new TreeSet();treeSet.add("dog");treeSet.add("camel");treeSet.add("cat");treeSet.add("ant");System.out.println(treeSet);
程序执行结果:[ant, camel, cat, dog]
可以看出,TreeSet 的使用与 Vector 类似地,只实现了自动排序。
6)LinkedHashSetLinkedHashSet 是按元素的 hashCode 该值决定了元素的存储位置,但同时使用链表来维护元素的顺序,使其看起来像是按插入顺序保存的。
LinkedHashSet 的使用与 Vector 类似。
集合与数组集合和数组的转换可以使用 toArray() 和 Arrays.asList() 为实现,请参考以下代码示例:
List<String> list = new ArrayList();list.add("cat");list.add("dog");// String[]集合转数组 arr = list.toArray(new String[list.size()]);// 数组转集List<String> list2 = Arrays.asList(arr);
集合与数组的区别可以参考「应用数组和排序算法 + 面试题」的内容。
集合排序在 Java 语言排序提供了两种方式:Comparable 和 Comparator,它们的区别也是常见的面试问题之一。让我们彻底了解一下 Comparable 和 Comparator 使用与差异。
1)ComparableComparable 位于 java.lang 包下是一个排序接口,也就是说,如果实现了一个类别 Comparable 接口意味着该类具有排序功能。
Comparable 界面只包含一个函数,定义如下:
package java.lang;import java.util.*;public interface Comparable { public int compareTo(T o);}
Comparable 使用示例,请参考以下代码:
class ComparableTest { public static void main(String[] args) { Dog[] dogs = new Dog[]{ new Dog(“老旺财”, 10), new Dog(“小旺财”, 3), new Dog(二旺财”, 5), }; // Comparable 排序 Arrays.sort(dogs); for (Dog d : dogs) { System.out.println(d.getName() + ":" + d.getAge()); } }}class Dog implements Comparable<Dog> { private String name; private int age; @Override public int compareTo(Dog o) { return age - o.age; } public Dog(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; }}
程序执行结果:
小旺财:32旺财:5老旺财:10
如果 Dog 类未实现 Comparable 执行代码会报告程序异常的信息,错误信息如下:
Exception in thread "main" java.lang.ClassCastException: xxx cannot be cast to java.lang.Comparable
compareTo() 返回值有三种:
- e1.compareTo(e2) > 0 即 e1 > e2;
- e1.compareTo(e2) = 0 即 e1 = e2;
- e1.compareTo(e2) < 0 即 e1 < e2。
Comparator 位于外部比较器的外部比较器 java.util 包下,之所以说 Comparator 它是一种外部比较器,因为它不需要在比较类中实现 Comparator 接口,但要创建一个新的比较器类来进行比较和排序。
Comparator 接口包含的主要方法是 compare()定义如下:
public interface Comparator<T> { int compare(T o1, T o2);}
Comparator 使用示例,请参考以下代码:
class ComparatorTest { public static void main(String[] args) { Dog[] dogs = new Dog[]{ new Dog(“老旺财”, 10), new Dog(“小旺财”, 3), new Dog(二旺财”, 5), }; // Comparator 排序 Arrays.sort(dogs,new DogComparator()); for (Dog d : dogs) { System.out.println(d.getName() + ":" + d.getAge()); } }}class DogComparator implements Comparator<Dog> { @Override public int compare(Dog o1, Dog o2) { return o1.getAge() - o2.getAge(); }}class Dog { private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; }}
程序执行结果:
小旺财:32旺财:5老旺财:10
相关面试题1.List 和 Set 有什么区别?答:区别分为以下几个方面:
- List 允许有多个 null 值,Set 只允许一个 null 值;
- List 允许重复元素,Set 重复元素不允许;
- List 能保证各元素的存储顺序,Set 元素的存储顺序无法保证。
答:TreeSet 集合实现元素的自动排序,即元素的自动排序功能无需任何操作即可实现。
3.Vector 和 ArrayList 初始化大小和容量扩展有什么区别?答:Vector 和 ArrayList 默认容量为 10、源代码如下。
Vector 默认容量源代码:
public Vector() { this(10);}
ArrayList 默认容量源代码:
private static final int DEFAULT_CAPACITY = 10;
Vector 默认增加容量扩张 1 倍,源代码如下:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity);}
其中 capacityIncrement 为初始化 Vector 默认情况为指定情况 0。
ArrayList 默认容量扩大的增加可能会增加 0.5 倍(oldCapacity + (oldCapacity >> 1),源代码如下(JDK 8):
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);}
4.Vector、ArrayList、LinkedList 有什么区别?答:这三者都是 List 因此,子类的功能相似,如添加和删除操作、搜索元素等,但在性能、线程安全等方面表现不同,差异如下:
- Vector 是 Java 它使用早期提供的动态数组 synchronized 为了保证线程安全,如果不推荐使用非线程安全,毕竟线程同步是有性能费用的;
- ArrayList 它是最常用的动态数组,线程本身并不安全,所以性能要好得多 Vector 同样,它也是动态调整容量的,只是 Vector 扩容会增加 1 倍,而 ArrayList 会增加 50%;
- LinkedList 它是一个双向链表集合,所以它不需要像上面两个那样调整容量,它也是一个非线程安全集合。
答:Vector 和 ArrayList 内部结构以数组形式存储,非常适合随机访问,但非尾部删除或新性能较差。例如,如果我们在中间插入一个元素,我们需要移动后续的所有元素。
LinkedList 插入和删除元素更有效,但随机访问性能比上述两个动态数组慢。
6.Collection 和 Collections 有什么区别?答:Collection 和 Collections 区别如下:
- Collection 它是一个集合的上级接口,主要继承它 List 和 Set;
- Collections 它提供了一些静态方法来实现集合类的帮助类,例如 Collections.sort() 排序、Collections.reverse() 逆序等。
A:ListB:SetC:MapD:HashSet
答:C
8.LinkedHashSet 如何保证有序性和独特性?答:LinkedHashSet 底层数据结构由哈希表和链表组成,链表保证了元素的有序存储和取出,哈希表保证了元素的独特性。
9.HashSet 如何确保数据不可重复?答:HashSet 其实底层就是 HashMap,只不过 HashSet 实现了 Set 并将数据作为接口 K 值,而 V 值总是用相同的虚拟值来保存,我们可以看到源代码:
public boolean add(E e) { return map.put(e, PRESENT)==null;// 调用 HashMap 的 put 方法,PRESENT 虚值}自始至终都是一样的
由于 HashMap 的 K 值本身不允许重复,而且在 HashMap 中如果 K/V 同时,会使用新的 V 覆盖掉旧的 V,然后返回旧的 V,那么在 HashSet 执行这句话总会一个一个回来的 false,导致插入失败,保证了数据的不可重复性。
10.执行以下程序会产生什么结果?为什么?Integer num = 10;Integer num2 = 5;System.out.println(num.compareTo(num2);
答:程序输出的结果是 1
,因为 Integer 默认实现了 compareTo 定义自然排序规则的方法,所以当 num 比 num2 大时会返回 1,Integer 相关源代码如下:
public int compareTo(Integer anotherInteger) { return compare(this.value, anotherInteger.value);}public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? -1 : ((x == y) ? 0 : 1);}
11.如何用程序实现先进先出的栈结构?答:集合中可以使用 Stack 实现,Stack 采用标准后进先出的栈结构 Stack 中的 pop() 方法返回栈顶元素并删除该元素,示例代码如下。
Stack stack = new Stack();stack.push("a");stack.push("b");stack.push("c");for (int i = 0; i < 3; i++) { // 移除并返回栈顶元素 System.out.print(stack.pop() + " ");}
程序执行结果:c b a
答:peek() 方法返回第一个元素,但不删除当前元素,当元素不存在时返回 null;poll() 方法返回第一个元素并删除该元素,当元素不存在时返回 null。
13.Comparable 和 Comparator 有什么区别?答:Comparable 和 Comparator 主要区别如下:
- Comparable 位于 java.lang 包下,而 Comparator 位于 java.util 包下;
- Comparable 在排序类内部实现, Comparator 外部实现排序类;
- Comparable 需要重写 CompareTo() 方法,而 Comparator 需要重写 Compare() 方法;
- Comparator 在类的外部实现,更加灵活方便。
本文介绍的所有集合都是自我实现的 Collection,所以它们都有相同的操作方法,比如 add()、addAll()、remove() 等,Collection 接口方法列表如下图所示:
当然,一些集合也扩展了自己独特的方法,比如原来的方法 LinkedList 的 offer()、push() 等方法。本文还提供了数组和集合互转的方法,List.toArray() 将集合转换为数组,Arrays.asList(array) 将数组转换为集合。最后介绍了 Comparable 和 Comparator 使用和差异,Comparable 和 Comparator 是 Java 语言排序提供了两种排序方法,Comparable 位于 java.lang 包下,如果实现了一个类别 Comparable 接口,这意味着这类有排序功能;而且 Comparator 位于 java.util 它是一种外部比较器,不需要在比较类中实现 Comparator 接口,但要创建一个新的比较器类来进行比较和排序。