Java集合框架面试题
标签(空格分隔): Java集合框架
屏幕快照 2017-04-03 上午8.41.30.png 屏幕快照 2017-04-03 上午8.42.10.png
面试题汇总
1. 什么是Java集合框架?请列举出集合(Collections framework)框架的一些好处?
屏幕快照 2017-04-03 上午7.45.45.png-101.1kB- 通过使用集合框架的核心类可以减少开发成本,从而避免实现自己的集合类。
- 使用经过良好测试的集合类可以提高代码的质量。
- 使用JDK自带的集合类可以减少代码维护成本。
- 良好的可读性和通用性。
2. 集合框架(Collections framework)中的泛型有什么好处?
泛型是Java 1.5中的新特性,所有的集合接口和实现类都大量地使用了泛型。泛型允许我们指定集合中对象的类型,因此试图向其中添加其他类型的元素时在编译期就会报错。这样就避免了运行时的ClassCastException异常并且我们不再进行类型转换和 instanceof判断,我们的代码更简洁了。此外有了泛型,也不会生成用于类型检查的字节码了,提高了运行时效率。
3. 列举出Java集合框架的一些基本接口?
- Collection是集合层次中的顶层接口。一个集合代表了一组对象。Java 平台不提供这个接口的任何实现。
- Set 是一种不能包含重复元素的集合。这个接口是对数学概念中的集合的一个抽象,用于表示某类事物的集合,如可以表示一副扑克牌。
- List 是一种有序的集合,可以包含重复的元素。可以通过索引访问元素,就像一种动态的数组。
- Map 是一种将key映射到value的集合。map对象不能包含重复的key:每个key至多可以映射到一个value。
- 还有一些其他的接口,如Queue,Dequeue,Iterator,SortedSet,SortedMap和ListIterator.
4. 为什么集合(Collection)不能继承Cloneable和Serializable接口?
Collection 接口将一组对象作为它的元素。然而元素是由具体的实现类来维护的。例如,List允许重复元素,而其他的实现类如Set并不允许这样做。许多 Collection接口的实现类都有一个public的clone()方法。但不是所有的实现类都包含此方法。因为Collection接口是一种抽象 的表示方法,表示的是一种契约、约定,对外暴露的“服务”,具体的实现类才有意义。对于cloning和serializing这些语义性的方法只有放到 具体的实现类中才有意义,也就是说,具体的实现类才能决定它是否应该被clone或serialized,甚至是它能否被clone或 serialized。
因此强制在所有的实现类中实现clone和serialization都会让灵活性降低并产生更多的局限性。具体的实现类才能决定它是否可以被clone或serialized。
5. 为什么Map接口不能继承Collection接口?
尽管Map接口及其实现类是Collection框架的一部分,但是要注意的是Map不是集合,集合也不是Map。因此Map继承Collection接口是无意义的,反之亦然。
如果Map接口继承了Collection接口,那么元素如何存放呢?Map保存的是键值对,提供了取得所有key或value集合的方法,但它体现不了“元素的集合”这种概念。
6. 什么是迭代器(Iterator)?
JDK1.8官方文档
An iterator over a collection. Iterator takes the place of Enumeration in the Java Collections Framework. Iterators differ from enumerations in two ways:
Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
Method names have been improved.
Iterator接口提供了用于遍历集合的方法。可以在集合对象上调用iterator()方法来获取一个迭代器的实例。Java集合框架中,Iterator接口取代了Enumeration接口,并且,迭代器允许在进行遍历时移除元素。
7. Enumeration和Iterator接口有何不同?
Enumeration的速度是Iterator的2倍并且消耗较少的内存, 它很简单,适合在基本需求(basic needs)下使用。但是相对于Enumeration,Iterator更安全,因为在对集合进行遍历时会阻止其他线程对集合进行修改。
Java集合框架中, Iterator取代了Enumeration。Iterator允许从集合中移除元素,而这对Enumeration来说是不可能的。而且Iterator接口中的方法名清晰明了,一看就知道是干什么用的。
8. 为什么没有类似Iterator.add()这样的方法来向集合中添加元素?
如果迭代器无法保证迭代的顺序,那么这种说法是不清晰的(unclear)。注意的是,ListIterator就提供了add()方法,因为它能保证迭代的顺序。
9. 为什么Iterator没有一个方法在不移动指针情况下直接获取下一个元素?
这可以在当前的Iterator接口之上进行实现,但是由于这种使用情况很少,所以把它包含在接口中几乎是无意义的,想要这种功能,自己实现即可。
10. Iterator与ListIterator有何不同?
- Iterator可以用来遍历Set和List集合,而ListIterator只能用于遍历List。
- Iterator只能进行正向(forward direction)遍历而ListIterator可以双向遍历。
- ListIterator继承于Iterator接口,但却添加了一些额外的功能,如添加、替换元素,获取前后元素的索引。
11. 迭代一个list有哪些不同的方法?
有两种方式:使用迭代器或用for-each循环。
List<String> strList = new ArrayList<String>();
//using for-each loop
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
使用iterator 是线程安全的,因为它会确保在遍历时集合没有被修改,否则会抛出ConcurrentModificationException异常。
12. 对iterator的快速报错(fail-fast)特性有何理解?
Iterator 的快速报错机制会在每次取下一个元素时检查集合是否发生了修改。若发现有修改,那么就抛出 ConcurrentModificationException异常。除了像 ConcurrentHashMap,CopyOnWriteArrayList这样的并发集合类,所有集合类的迭代器都是按fail-fast这种机制 进行实现的。
13. fail-fast与fail-safe的区别是什么?
Iterator的fail- safe是对集合在进行clone时提供的一种保护机制,因此集合发生了修改不会引起任何问题。从设计上来说,java.util包中的集合类都是 fail-fast机制的,而java.util.concurrent包中的集合类都是fail-safe的。Fail-fast机制实现的 iterator会抛出ConcurrentModificationException异常,而fail-safe机制的iterator永远不会抛出 ConcurrentModificationException异常。
14. 在迭代一个集合时如何避免ConcurrentModificationException?
可以使用并发集合类来遍历集合从而避免ConcurrentModificationException异常。例如, 使用CopyOnWriteArrayList 来替代 ArrayList.
15. 为什么Iterator接口没有具体的实现?
Iterator 接口只是用于声明迭代集合的方法,但它的实现是依赖于具体的类的。 每个集合类都通过嵌套类的方式实现了自己的Iterator接口从而返回用于遍历的iterator对象。这样可以让集合类自己选择迭代器的实现是 fail-fast还是fail-safe机制。例如,ArrayList的迭代器就是fail-fast,而CopyOnWriteArrayList 的迭代器则是fail-safe的。
16. 什么是UnsupportedOperationException?
UnsupportedOperationException 一种用于指示当前对象不支持调用某种方法的异常。JDK中广泛地使用了这种异常,在集合框架中,如果是通过 java.util.Collections.UnmodifiableCollection()方法创建的集合,那么对集合对象调用add()或 remove()方法都会抛出此异常。
17. Java中HashMap的工作原理?
18. hashCode()和equals()方法的意义?
HashMap 会调用key对象的hashCode() 和equals() 方法来决定”键值对”的索引位置。当从HashMap中取值时这两个方法也会被调用。如果这两个方法不能正确地实现的话,那么对于两个不同的 key,hashCode()和equals()方法也许会产生相同的的输出,从而不会将它们存放到不同的位置了,HashMap将它们看作一样的,因此 一个对象会被另一个覆盖。同理,其他哪些不允许存放重复元素的集合类都回调用hashCode()和equals()方法来判断元素是否重复,因此,正确 地实现这两个方法是非常重要的。要实现它们,请遵循以下原则:
If o1.equals(o2), 那么o1.hashCode() == o2.hashCode()总为真。
If o1.hashCode() == o2.hashCode()为真, 并不意味着 o1.equals(o2)也为真。
19. 可以用任意类型来作为Map的key吗?
Map允许使用的任意的类型作为 Key,然而在使用前请仔细考虑以下方面:
- 如果类重写了equals()方法,那么它也应该重写hashCode()方法。
- 类应总是遵循与equals() 和hashCode()相关的那些规则,具体请看上一个问题。
- 如果类中的某个字段并没有在equals()方法中使用,那么它也不应该出现在hashCode()方法中。
- 如果用户想要用自己的类来做Map的键时,最好能确保key是不可变的(immutable),这样hashCode()方法就会对其进行缓存从而提高性 能。并且不可变的类型也会确保在运行时hashCode() and equals()方法的值出也不会改变,从而避免不必要的麻烦。
20. Map接口中提供了哪些与Collection类不同的方法?
- Set keySet(): 返回map中key的集合。这个集合是与map相关联的,因此任何对map的修改都会反应到set上,反之亦然。如果在迭代集合时map被修改了(除了用 迭代器的remove()方法),那么迭代的结果是未定义的(undefined)。 set支持移除操作,所以可以通过Iterator.remove, Set.remove, removeAll, retainAll方法来删除“键”,同时map中的键值对也会被删除。但set不支持add()或 addAll()方法。
- Collection values(): 返回map中value的集合。这个集合也是与map相关联的,任何对map的修改都会反应到set上,反之亦然。如果在迭代集合时map被修改了(除了 用迭代器的remove()方法),那么迭代的结果是未定义的(undefined)。 set支持移除操作,所以可以通过Iterator.remove, Collection.remove, removeAll, retainAll,clear方法来删除“值”,同时map中的键值对也会被删除。但set不支持add()或 addAll()方法。
- Set<Map.Entry<K, V>> entrySet(): 返回map中键值对的集合。这个集合也是与map相关联的,任何对map的修改都会反应到set上,反之亦然。如果在迭代集合时map被修改了(除了用迭 代器的remove()方法或setValue()方法),那么迭代的结果是未定义的(undefined)。set支持移除操作,所以可以通过 Iterator.remove,Set.remove, removeAll, retainAll,clear方法来删除“键值对”。但set不支持add()或 addAll()方法。
21. HashMap与Hashtable有何区别?
HashMap 和 Hashtable 都实现了Map接口,看起来很相似,但它们之间有如下的区别:
- HashMap的键值均可为null,而Hashtable不允许键或值为null。
- Hashtable是synchronized的 而HashMap 则不是同步的。因此,HashMap适用于单线程环境下,Hashtable适合在多线程环境下使用。
- Java 1.4中引入LinkedHashMap作为HashMap的子类,因此可以进行有序的迭代,并且可以很轻松的从 HashMap 过渡到 LinkedHashMap,然而 Hashtable的迭代顺序是无法预测的。
- HashMap 可以对Key的集合进行迭代因此受fail-fast机制保护,而Hashtable只提供了键的Enumeration,因此无法实现这样的机制。
- Hashtable是一种比较基础的类,如果想在迭代时对map进行修改,那么应该使用 ConcurrentHashMap。
22. 怎样决定何时使用HashMap何时使用TreeMap?
对 于插入、删除、定位元素频繁的操作,HashMap提供了最好的效率。如果想要按key的排序来遍历,那么TreeMap是不二选择。某些情况下,依赖集 合的大小,先向HashMap中添加元素,然后转换为TreeMap再按key的排序进行遍历也许会带来效率上的提高。
23. ArrayList和Vector的相同点和不同点?
- Vector 是同步的而ArrayList不是同步的。如果想要在迭代list时对其进行修改,那么应该选用CopyOnWriteArrayList类。
- ArrayList 比 Vector 的速度快,因为它没有同步问题带来的等待。
- ArrayList 是个多面手,因为我们可以很容易的通过使用Collections工具类来获得新的同步的list或只读的(read-only)。
- 两个类都是基于索引访问的并且在内部都是通过数组保存元素。
- 两个类都按插入顺序来维护元素顺序,从而可以按插入顺序来获取元素。
- 它们的迭代器都按fail-fast机制进行实现。
- ArrayList Vector 都允许插入null,并且可以按索引进行随机访问。
24. Array和ArrayList的区别?哪些情况下优先选择Array而不是ArrayList?
-
Array 可以包含基本类型或对象类型的元素,而ArrayList 只能包含对象类型的元素。
-
Array 大小是固定的而 ArrayList 大小是动态的。
-
Array 不像ArrayList那样提供很多特性,如addAll, removeAll, iterator方法等等。尽管大多数情况下我们都使用ArrayList,可是在少数情况下Array也是一个不错的选择,情况如下:
-
如果list的大小固定并且大多数情况是用于存储和遍历集合。
-
如果list中要包含基本类型的数据,那么Array是不错的选择,尽管Collections使用自动装箱机制会简化我们的代码量,但它的效率仍不如具有固定大小的可以包含基本类型的Array。
-
如果要操作多维的数据,那么使用[][]要比List<List<>>容易的多。
25. ArrayList和LinkedList的区别?
- ArrayList 是一种基于索引的数据结构,底层是以Array来实现的,因此可以随机访问其中的元素,性能为 O(1) ,但LinkedList 是以链表的方式来存储数据的,每个节点都首尾相连。因此尽管有按索引取回元素的方法,但其内部实现还是从头遍历,然后返回元素,因此其性能是O(n),比 ArrayList要慢。
- 相对于ArrayList,LinkedList在插入,添加或删除元素时就快得多了,因为LinkedList的实现不依赖数组,向集合中间添加元素时就不会有自动扩容和更新索引的麻烦了,所以速度要快。
- LinkedList比ArrayList占用更多的内存,因为LinkedList中的每个节点都存储着前后元素的引用。
26. 哪些集合(collection)类提供了随机访问元素的功能?
ArrayList, HashMap, TreeMap, Hashtable 类都具有随机访问元素的特性。
27. 什么是EnumSet?
java.util.EnumSet 是对Set接口的一种实现,使用了枚举(enum)类型。 All of the elements in an enum set must come from a single enum type that is specified, explicitly or implicitly, when the set is created. EnumSet不是同步的,null不允许添加进去。它也提供了一些有用的方法,如copyOf(Collection c), of(E first, E… rest) 和 complementOf(EnumSet s)。
28. 哪些集合(collection)类是线程安全的?
Vector, Hashtable, Properties and Stack都是同步的(synchronized)类,因此是线程安全的,可以在多线程环境下使用. Java 1.5 Concurrent API 引入了一些允许在迭代过程中可以修改集合的并发类,由于它们会在迭代时会clone一份集合,所以在多线程环境下也是安全的。
29. 并发集合(concurrent Collection)类有哪些?
Java 1.5 Concurrent包(java.util.concurrent) 引入了线程安全的集合类,允许在迭代时对集合进行修改。这些类的迭代器都是按fail-fast机制进行实现的,会抛出 ConcurrentModificationException异常。常见的类有 CopyOnWriteArrayList,ConcurrentHashMap,CopyOnWriteArraySet等等。
30. 什么是BlockingQueue?
java.util.concurrent.BlockingQueue 是Queue接口的子接口,支持获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。BlockingQueue接口是java集合框架的一部 分,它主要用于处理生产者、消费者问题。对于生产者不必再关心是否还有存储空间,对于消费者不必再关心是否有对象可以消费,所有这些细节都由具体的实现类 处理了。BlockingQueue接口有几个实现类,如ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue等等。
31. 什么是Queue,Stack以及它们的区别?
Queue 和Stack 都是用于存储临时数据的。java.util.Queue是一个接口,其实现类大多位于concurrent包中。 Queue 可以按FIFO的顺序取回元素,但并不是所有的queue都遵循这个原则。如Deque接口就可以从队列的两端访问元素。
Stack 与 queue 类似,但它是按LIFO的顺序访问元素。
Stack是一个类,继承了Vector类,而Queue只是一个接口。
32. Collections类有什么作用?
java.util.Collections 是一个工具类(utility),有许多用于操作、返回集合的静态方法。 它包含了对集合操作的各种算法,其中较常见的是“包装器(wrappers)”,可以根据指定的集合返回一个新集合,还有许多其他的一些零碎的用法这里不 再赘述。这个类实现了许多针对集合操作的算法,如二叉搜索、排序、洗牌(shuffling)、逆序(reverse)等等。
33. 怎样理解Comparable, Comparator 接口?
如 果想要调用Array或Collection类的排序方法,那么任何自定义类都需要实现Comparable接口。排序方法会调用Comparable 接口中的compareTo(T obj) 方法。我们要重载此方法,以返回负整数,0,正整数来表示当前对象(this)是否小于,等于或大于传进来的对象。但是,在实际的应用中,我们通常有基于 不同的参数进行排序的需求。例如,CEO想要按职工的薪水进行排序,而HR希望按年龄进行排序。在这种情况下,我们就需要使用Comparator接口 了,因为Comparable接口的compareTo(Object o)方法的实现通常都是基于一个字段(field),无法对其他的属性进行排序。而Comparator接口的compare(Object o1, Object o2)方法接收两个Object类型的参数,因此在实现此方法时可以根据不同的属性进行实现,返回负整数,0,正整数来表示第一个对象小于、等于或大于第 二个对象。
34. Comparable 与 Comparator 接口的区别?
Comparable 和 Comparator 接口都用于对集合或数组内的对象进行排序。Comparable 接口提供了对对象的自然排序(natural sorting)方法,我们也可以基于单一的逻辑(single logic)实现排序方法。
Comparator 接口用于提供普通的排序算法,以至于我们可以选择不同的comparator来对给定的集合进行排序。
35. 如何对list中的对象进行排序?
想 要对数组内的对象进行排序,只需调用Arrays.sort()方法。想要对list中的对象进行排序,只需调用Collections.sort()方 法。但是,这两种方法要么实现了Comparable接口的sort()方法,对集合进行自然排序,要么实现了Comparator接口中的sort() 方法,能按某些条件(criteria)进行排序。其实Collections内部会调用Array的排序方法,因此除了在排序前Collections 类会将list中的元素先复制到数组中消耗点时间外,它们在排序性能上几乎没有差别。
36. 当集合作为参数传入方法中时,如何确保方法不会修改它?
我 们可以通过调用Collections.unmodifiableCollection(Collection c)方法来创建一个只读的集合,然后再将它传入函数中,这样就会确保任何企图修改集合的操作都会引起 UnsupportedOperationException异常。
37. 根据给定的集合如何创建一个synchronized的集合?
可以调用Collections.synchronizedCollection(Collection c)方法,会返回一个同步的集合。
38. Collections框架都实现了哪些常见的算法?
Java Collections 框架提供了常见的像排序和搜索算法的实现。这些方法都在具体的类中进行实现。大多数这些算法都用于操纵List,但有一部分是适用于所有的集合类的。这些 算法有排序,搜索,洗牌(shuffling),极值(min-max values)等。
39. 什么是大O符号(Big-O notation)?请举例?
大O符号用于描述某种算法的性能,这些算法通常被应用于操作包含大量元素的数据结构。由于集合类实际上就是对数据结构的实现,因此大多数情况下,我们都从时间、空间和性能等方面的开销来选择合适的容器,而大O符号就是对这三方面的一种衡量。
例1:ArrayListget(index i)方法的执行时间为常数,并不依赖list的大小,因此用大O符号表示为O(1)。
例2:对数组或链表进行查找,其性能都是O(n),因为我们需要遍历整个集合才能找到元素。
40. Java集合框架都有哪些最佳实践?
- 要根据需求来选择正确的容器类型,例如,如果大小固定,也许选择Array就比ArrayList更明智。如果想按插入顺序对Map进行迭代,那么需要选TreeMap。如果集合不允许重复,那么就应该选Set。
- 某些集合类允许指定初始容量,如果我们能粗略的估计下要存储元素的数量,那么我们就可能避免重新hash(rehashing)和自动扩容(resizing)所带来的某些性能问题。
- 要针对接口编程而不是针对实现编程,这允许我们以后可以轻松的变更实现。
- 要尽可能的使用泛型以保证类型安全,从而避免运行时的ClassCastException异常。
- 使用JDK提供的不可变的(immutable)类型来作为Map的键,从而避免自己实现hashCode() 和equals()方法。
- 尽可能多的使用Collections这一工具类,因为我们可以很轻松的调用它已实现的算法或用来获取只读的、同步的或空的集合对象,从而避免自己实现。这会极大的提高代码的复用性、稳定性,降低维护成本。