java面试突破 - 集合面试汇总

目录

1. 简述Java中的集合

Java 集合 有两大模块!

  • Collection 下 :
    List系(有序、元素可重)
    Set系(无序、元素不可重)

    set 根据 equals 和 hashcode判断(是否重复)
    一个对象要存储在set中,必须重写 equals 和 hashCode 方法!

  • Map下:
    HashMap 不同步
    TreeMap 同步

2. List、Map、Set三个接口,存取元素时,各有什么特点?

首先,List 和 Set 类似,都是单例元素的集合!有相同父接口:Collection

1、Set 里面 元素 不可重复

  • 即不能有两个 相等 (不仅仅是相同)的对象,即假设Set集合中有了一个A对象,现在我要向Set集合再存入一个B对象,但B对象与A对象equals相等,则B对象存储不进去。
  • Set 集合的 add():当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true,当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。
  • 遍历:不能根据下标取!只能以 Iterator 接口取得所有的元素,再逐一遍历各个元素!

2、List 表示有先后顺序的集合

  • add() 方法
    add(Obj e):按先来后到的顺序,排到最后!
    add(int index,Obj e):插入指定位置,其他后移!
    本质:用索引变量指向插入的对象!
  • 查询:
    用Iterator接口取得所有的元素,再逐一遍历各个元素
    get(index):根据下标获取

3、 Map 与 List 、Set 不同

map 是两列的集合!

  • put(obj key,obj value):不能存储重复的key,这个重复的规则也是按equals比较相等。
  • get(Object key),也可以获得所有的key的结合,还可以获得所有的value的结合,还可以获得key和value组合成的Map.Entry对象的集合。
  • 总结

List以特定次序来持有元素,可有重复元素。Set无法拥有重复元素,内部排序。Map保存key-value值,value可多值。

4. ArrayList和LinkedList区别?(从底层出发,未细说)

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

5. ArrayList和Vector的区别

都实现接口Collection -> List 接口,都是有序集合,可以按位置取! 这是和Set系 最大的两个不同:重复、取值!

  • ArrayList与Vector的区别主要包括两个方面:

同步性
Vector 是线程安全的!
ArrayList 是线程不安全的!

扩容机制
Vector默认扩容2倍;而ArrayLsit默认扩容1.5倍!

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

ArrayListVector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢

Vector 由于使用了 synchronized 方法(线程安全),通常性能上较 ArrayList 差。而 LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,索引就变慢了,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快

LinkedList 也是线程不安全的, LinkedList 实现了Deque(双端队列)接口,使得 LinkedList 可以被当作堆栈和队列来使用。

public class LinkedList<E> extends AbstractSequentialList<E> 
	implements List<E>, Deque<E>, Cloneable, Serializable
  • 队列使用:
    Deque queue = new LinkedList();
    offer()插入
    poll()方法删除:从队首获取元素,同时获取的这个元素将从原队列删除;
  • 当做栈列使用:
    Deque stack = new LinkedList();
    push() :添加!
    pop()弹出:表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常;

注意:使用peek()查询!

7. HashMap和Hashtable的区别

HashMapHashtable轻量级实现(非线程安全的实现)!都完成了Map接口

主要区别在于 HashMap 允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于 Hashtable

HashMap 允许将null作为一个entry的key或者value,而 Hashtable 不允许。

HashMapHashtablecontains 方法去掉了,改成 containsvalue 和 containsKey 。因为contains方法容易让人引起误解。

Hashtable 继承自 Dictionary 类,而 HashMap 是Java1.2引进的Map interface的一个实现。

最大的不同是(同步与否), Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供同步。

  • 总结(三个方面)

历史原因: Hashtable 是基于陈旧的 Dictionary 类的, HashMap 是Java 1.2引进的Map接口的一个实现
同步性: Hashtable 是线程安全的,也就是说是同步的,而 HashMap 是线程序不安全的,不是同步的
:只有 HashMap 可以让你将空值作为key或value
快速失败 (见20题 ):HashMap提供对key的Set进行遍历,因此它是fail-fast的,但Hashable提供对key的Enumeration进行遍历,它不支持fail-fast

8. Java中的同步集合与并发集合有什么区别?

同步集合并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。
Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。
Java5介绍了并发集合 ConcurrentHashMap ,不仅提供线程安全还用锁分离内部分区等现代技术提高了可扩展性。

不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能可扩展性,还有他们如何实现的线程安全上。

同步 HashMap , Hashtable , HashSet , Vector , ArrayList 相比他们并发的实现(ConcurrentHashMap , CopyOnWriteArrayList , CopyOnWriteHashSet )会慢得多。造成如此慢的主要原因是, 同步集合会把整个Map或List锁起来,而并发集合不会。并发集合实现线程安全是通过使用先进的和成熟的技术锁剥离

  • 比如 ConcurrentHashMap 会把整个Map 划分成几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段。(分段上锁!)
  • 同样的, CopyOnWriteArrayList 允许多个线程以非同步的方式读,当有线程的时候它会将整个List复制一个副本给它。(非同步读+复制写)

如果在读多写少这种对并发集合有利的条件下使用并发集合,这会比使用同步集合更具有可伸缩性

8. Java中的集合及其继承关系

  • 体系图!!!
    在这里插入图片描述

9. poll()方法和remove()方法区别?

poll() 和 remove() 都是从队列中取出一个元素

poll() 在获取元素失败的时候会返回空
remove() 失败的时候会抛出异常

10. LinkedHashMap和PriorityQueue的区别

LinkedHashMap 维持的顺序是元素插入的顺序;
PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部(小/大顶堆)。

当遍历一个 PriorityQueue 时,没有任何顺序保证;
但是 LinkedHashMap 能保证遍历顺序是元素插入的顺序。

11. WeakHashMap与HashMap的区别是什么?

WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收

12. ArrayList和HashMap默认大小?

ArrayList 的默认大小是 10 个元素,
HashMap 的默认大小是 16 个元素(必须是2的幂)

private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

13. Comparable和Comparator接口是什么?

  • 如果我们想使用Array或Collection的排序方法时,需要在自定义类里实现Java提供Comparable接口

Comparable接口compareTo(T OBJ)方法,它被排序方法所使用。我们应该重写这个方法,如果“this”对象比传递的对象参数更小、相等或更大时,它返回一个负整数、0或正整数

14. Comparator和Comparable的区别?

  • 相同点

都是用于比较两个对象“顺序”的接口
都可以使用Collections.sort()方法来对对象集合进行排序

  • 不同点

所属包不同Comparable位于java.lang包下,而Comparator则位于java.util包下
Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序

  • 总结

使用Comparable接口来实现对象之间的比较时,可以使这个类型(设为A)实现Comparable接口,并可以使用**Collections.sort()**方法来对A类型的List进行排序,之后可以通过a1.comparaTo(a2)来比较两个对象;

当使用Comparator接口来实现对象之间的比较时,只需要创建一个实现Comparator接口的比较器(设为AComparator),并将其传给Collections.sort()方法即可对A类型的List进行排序,之后也可以通过调用比较器AComparator.compare(a1, a2)来比较两个对象。

可以说一个是自己完成比较,一个是外部程序实现比较的差别而已。
Comparator策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。

使用Comparable方式比较时,我们将比较的规则写入了比较的类型中,其特点是高内聚。但如果哪天这个规则需要修改,那么我们必须修改这个类型的源代码

如果使用Comparator方式比较,那么我们不需要修改比较的类,其特点是易维护,但需要自定义一个比较器,后续比较规则的修改,仅仅是这个比较器中的代码即可。

15. 如何实现集合排序?

你可以使用有序集合,如 TreeSetTreeMap,你也可以使用有顺序的的集合,如 list,然后通过Collections.sort() 来排序。

16. TreeMap的实现原理

TreeMap 是一个通过红黑树实现有序的 key-value 集合。
TreeMap 继承 AbstractMap,也即实现了Map,它是一个Map集合。
TreeMap实现了 NavigableMap 接口,它支持一系列的导航方法,
TreeMap实现了 Cloneable 接口,它可以被克隆

TreeMap本质是 Red-Black Tree,它包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据Key排序,包含的内容是value。Entry中key比较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数

17. 遍历ArrayList时如何正确移除一个元素

错误写法示例一:

public static void remove(ArrayList<String> list) {
	for (int i = 0; i < list.size(); i++) {
		String s = list.get(i);
		if (s.equals("bb")) {
			list.remove(s);
		}
	}
}

错误写法示例二:

public static void remove(ArrayList<String> list) {
	for (String s : list) {
		if (s.equals("bb")) {
			list.remove(s);
		}
	}
}

源码分析
ArrayList 中的 remove方法进行了重载!

  • remove(Obj)
public boolean remove(Object o) {
	if (o == null) {
		for (int index = 0; index < size; index++)
		if (elementData[index] == null) {
		fastRemove(index);
		return true;
	}
	} else {
		for (int index = 0; index < size; index++)
			if (o.equals(elementData[index])) {
				fastRemove(index);
				return true;
			}
	}
	return false;
}

一般 最终调用 faseRemove 方法

private void fastRemove(int index) {
	modCount++;
	int numMoved = size - index - 1;
	if (numMoved > 0)
		System.arraycopy(elementData, index+1, elementData, index,numMoved);
	elementData[--size] = null; // Let gc do its work
}

可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动

错误写法一:在遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免;因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。

错误写法二:foreach写法是对实际的 Iterable、hasNext、next方法的简写,问题同样处在上文的 fastRemove 方法中,可以看到第一行把modCount变量的值加一,但在 ArrayList 返回的迭代器(该代码在其父类AbstractList中):

public Iterator<E> iterator() {
	return new Itr();
}

这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的 next 方法

public E next() {
	checkForComodification();
	try {
		E next = get(cursor);
		lastRet = cursor++;
		return next;
	} catch (IndexOutOfBoundsException e) {
		checkForComodification();
		throw new NoSuchElementException();
	}
}

第一行checkForComodification方法(modCount 改变 抛出异常!):

final void checkForComodification() {
	if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
}

这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

public static void remove(ArrayList<String> list) {
	Iterator<String> it = list.iterator();
	while (it.hasNext()) {
		String s = it.next();
		if (s.equals("bb")) {
			it.remove();
		}
	}
}

18. HashMap的实现原理 (必考)

原理:1.8之前:数组+链表;1.8之后:数组+链表+红黑树。(都是为了解决hash冲突)

内部存储结构:1.8之前:Entry<K,V>;1.8之后:Node<K,V>。

static class Node<K,V> implements Map.Entry<K,V> {
	final int hash;
	final K key;
	V value;
	Node<K,V> next;
	...
}

可以看见Node类中除了键值对(key-value)以外,还有额外的两个数据:

  • hash : 这个是通过计算得到的散列值
  • next:指向另一个Node,这样HashMap可以像链表一样存储数据
  • 内部变量

// 默认容量大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 转换为二叉树的阀值(这也是为什么链表长为8时会转换为红黑树)
static final int TREEIFY_THRESHOLD = 8;
// 转换为二叉树的最低阀值
static final int UNTREEIFY_THRESHOLD = 6;
// 二叉树最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 哈希表
transient Node<K,V>[] table;
// 键值对的数量
transient int size;
// 记录HashMap结构改变次数,与HashMap的快速失败相关
transient int modCount;
// 扩容的阈值
int threshold;
// 装载因子
final float loadFactor;

  • 常用方法

put操作: hash获得下标->封装为对象(Node/Entry)->插入(是否扩容)
(注意以下源码)

else if (p instanceof TreeNode) // 如果当前p是二叉树,则放入二叉树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash); 
// 如果链表中的值大于(转换为二叉树的阈值)TREEIFY_THRESHOLD - 1,则将链表转换成二叉树

1.8之前:头插法!O(n)
1.8之后:尾插链表+插入红黑树 O(logn)

get操作: 思路如下:

  1. bucket里的第一个节点,直接命中;
  2. 如果有冲突,则通过key.equals(k)去查找对应的entry
  3. 若为树,则在树中通过key.equals(k)查找,O(logn);
  4. 若为链表,则在链表中通过key.equals(k)查找,O(n)。

19. HashMap自动扩容

默认大小为16 ,当达到条件时调用resize()方法,扩大HashMap容量!

扩容条件loadFactor(扩容因子) 的默认值为 0.75 ;即默认情况下:hashMap的元素个数超过 默认大小(16)* 0.75 = 12 时,进行扩容!

HashMap 的 capacity 必须满足是 2的N次方 ,如果在构造函数内指定的容量n不满足,HashMap会通过下面的算法将其转换为大于n的最小的2的N次方数。(tableSizeFor方法:移位+或)有兴趣了解的同学可以参考:https://www.cnblogs.com/xiyixiaodao/p/14483876.html

// 减1→移位→按位或运算→加1返回
static final int tableSizeFor(int cap) {
	int n = cap - 1;
	n |= n >>> 1;
	n |= n >>> 2;
	n |= n >>> 4;
	n |= n >>> 8;
	n |= n >>> 16;
	return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

20. HashMap线程安全吗?

是非线程安全的!多线程环境可使用Hashtable!

Java5 以后,出现了 ConcurrentHashMap
ConcurrentHashMap 相对于 Hashtable 来说:
将 hash 表分为16个桶(默认值),诸如 get、 put、 remove 等常用操作只锁住当时使用的桶

  • 快速失败(fast-fail)

是Java集合的一种错误检测机制!当多个线程对集合上的结构进行改变是,可能会产生 fast-fail!(是可能,不是一定)。
例如:
假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException 异常,从而产生fail-fast机制。

  • fast - fail 源码解析(HashMap 的 forEach方法)!
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
	Node<K,V>[] tab;
	if (action == null)
		throw new NullPointerException();
	if (size > 0 && (tab = table) != null) {
		int mc = modCount;
		for (int i = 0; i < tab.length; ++i) {
			for (Node<K,V> e = tab[i]; e != null; e = e.next)
				action.accept(e.key, e.value);
		}
		if (modCount != mc)
			throw new ConcurrentModificationException();
	}
}

从上我们可以知道!modCount 是记录每次HashMap结构修改!forEach方法会在在进入for循环之前,将modCount赋值给mc,如果在for循环之后,HashMap的结构变化了,那么导致的结果就是modCount != mc,则抛出ConcurrentModificationException()异常。

21. HashMap总结

1、什么时候会使用HashMap?他有什么特点? 是基于Map接口的实现,存储键值对时,它可以接收 null 的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。

工作原理+底层实现
get、put的原理!
扩容机制!

22. Java集合框架是什么?说出一些集合框架的优点?

每种编程语言中都有集合。集合框架的部分优点如下:
1、使用核心集合类降低开发成本,而非实现我们自己的集合类。
2、随着使用经过严格测试的集合框架类,代码质量会得到提高。
3、通过使用JDK附带的集合类,可以降低代码维护成本
4、复用性和可操作性

23. 集合框架中的泛型有什么优点?

Java1.5 引入了泛型,所有的集合接口和实现都大量地使用它。

泛型允许我们为集合提供一个可以容纳的对象类型
如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException

24. 为何Collection不从Cloneable和Serializable接口继承?

克隆(cloning)或者是序列化(serialization)语义和含义是跟具体的实现相关的。
因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。

25. 为何Map接口不继承Collection接口?

尽管Map接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map 。因此,Map 继承 Collection 毫无意义,反之亦然。

如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。

26. Iterator是什么?

Iterator接口提供遍历任何 Collection 的接口。
可以从一个 Collection中 使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素(17的最后有提到!)。

27. Iterator和ListIterator的区别是什么?

  • Iterator可用来遍历Set和List集合
    但是ListIterator只能用来遍历List
  • Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向
  • ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

28. Enumeration和Iterator接口的区别?

  • 源码
package java.util;
 
public interface Enumeration<E> {
 boolean hasMoreElements();
 E nextElement();
}
public interface Iterator<E> {
 boolean hasNext();
 E next();
 void remove();
}
  • 优点:Enumeration速度是Iterator的2倍,同时占用更少的内存。
  • 缺点:

函数接口不同:
Enumeration 只有2个函数接口。 通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
Iterator 只有3个函数接口。 Iterator除了能读取集合的数据之外,也能数据进行删除操作

Iterator 支持 fail-fast 机制,而 Enumeration 不支持:
EnumerationJDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。

IteratorJDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
所以说iterator 是安全的

28. 为何没有像Iterator.add()这样的方法,向集合中添加元素?

语义不明,已知的是,Iterator的协议不能确保迭代的次序。然而要注意,ListIterator没有提供一个add操作,它要确保迭代的顺序。

29. 为何迭代器没有一个方法可以直接获取下一个元素,而不需要移动游标?

它可以在当前Iterator的顶层实现,但是它用得很少,如果将它加到接口中,每个继承都要去实现它,这没有意义

30. 遍历一个List有哪些不同的方式?

  • 代码:
List<String> strList = new ArrayList<>();
//使用for-each循环
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);
}

使用迭代器更加线程安全,因为它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。

31. 通过迭代器fail-fast属性,你明白了什么?

每次我们尝试获取下一个元素的时候,Iterator fail-fast属性检查当前集合结构里的任何改动。如果发现任何改动,它抛出ConcurrentModificationException。Collection中所有Iterator的实现都是按fail-fast来设计的(ConcurrentHashMapCopyOnWriteArrayList这类并发集合类除外)。

32. fail-fast与fail-safe有什么区别?

Iterator安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响java.util 包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

并发集合和同步集合的区别!

33. 在迭代一个集合的时候,如何避免ConcurrentModificationException?

使用并发集合 来避免ConcurrentModificationException,因为是 **fail-safe(安全失败)**的!如:ConcurrentHashMap、CopeOnWriteArrayList!

34. 为何Iterator接口没有具体的实现?

Iterator接口定义了遍历集合的方法,但它的实现则是集合实现类的责任。(具体的类的遍历方式不同)。每个能够返回用于遍历的Iterator的集合类都有它自己的Iterator实现内部类

这就允许集合类去选择迭代器是fail-fast还是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的。

35. UnsupportedOperationException是什么?

UnsupportedOperationException是用于表明操作不支持的异常。
在JDK类中已被大量运用,在集合框架java.util.Collections.UnmodifiableCollection将会在所有add和remove操作中抛出这个异常。

36. 我们能否使用任何类作为Map的key?

我们可以使用任何类作为Map的key,然而在使用它们之前,需要考虑以下几点:

(1)如果类重写了equals()方法,它也应该重写hashCode()方法
(2)类的所有实例需要遵循与equals()和hashCode()相关的规则。请参考之前提到的这些规则。
(3)如果一个类没有使用equals(),你不应该在hashCode()中使用它。
(4)用户自定义key类的最佳实践是使之为不可变的,这样,hashCode()值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode()和equals()在未来不会改变,这样就会解决与可变相关的问题了。

例如:我有一个类MyKey,在HashMap中使用它。

//传递给MyKey的name参数被用于equals()和hashCode()中 
MyKey key = new MyKey('Pankaj');
//assume hashCode=1234 
myHashMap.put(key, 'Value'); 
// 以下的代码会改变key的hashCode()和equals()值 
key.setName('Amit'); 
//assume new hashCode=7890 
//下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null 
myHashMap.get(new MyKey('Pankaj')); 
//那就是为何String和Integer被作为HashMap的key大量使用。

37. Map接口提供了哪些不同的集合视图?

Map接口提供三个集合视图(三个返回值):

1、Set keyset()返回map中包含的所有key的一个Set视图

2、Collection values()返回一个map中包含的所有value的一个Collection视图

3、Set<Map.Entry<K,V>> entrySet()返回一个map钟包含的所有映射的一个集合视图。

38. 如何决定选用HashMap还是TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。
然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。
基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

39. 哪些集合类提供对元素的随机访问?

  • ArrayList、HashMap、TreeMap和Hashtable类提供对元素的随机访问。

40. EnumSet是什么?

java.util.EnumSet是使用枚举类型的集合实现。

当集合创建时,枚举集合中的所有元素必须来自单个指定的枚举类型,可以是显示的或隐示的。EnumSet是不同步的不允许值为null的元素。它也提供了一些有用的方法,比如copyOf(Collection c)of(E first,E…rest)complementOf(EnumSet s)

41. 哪些集合类是线程安全的?

  • 同步集合(util包下):Vector、HashTable、Properties和Stack是同步类,所以它们是线程安全的。
  • 并发集合(concurrent包下):ConcurrentHashMap、CopeOnWriteArrayList、CopyOnWriteArraySet。由于是复制修改,是fail-safe的!

42. BlockingQueue是什么?

是一个阻塞队列,位于concurrent包下!
在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间

BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。(内部进行等待(阻塞))

  • Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue,、SynchronousQueue等。

43. Collections类是什么?

Java.util.Collections是一个工具类仅包含静态方法,它们操作或返回集合。

  • 它包含操作集合的多态算法,返回一个由指定集合支持的新集合和其它一些内容。这个类包含集合框架算法的方法,比如折半搜索、排序、混编和逆序等。

44. 我们如何对一组对象进行排序?

数组排序:Arrays.sort()
对象列表排序:Collections.sort()

  • 相同点:

都有用于自然排序(使用Comparable)或基于标准的排序(使用Comparator)的重载方法sort()
Collections.sort():内部使用数组排序方法!!

  • 不同点:

Collections需要花时间将列表转换为数组
Sort方法!分别使用:Comparable、Comparator

45. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?

在作为参数传递之前,我们可以使用Collections.unmodifiableCollection(Collection c) 方法创建一个 只读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。

46. 我们如何从给定集合那里创建一个synchronized的集合?

我们可以使用 Collections.synchronizedCollection(Collection c) 根据指定集合来获取一个synchronized(线程安全的)集合。

47. 与Java集合框架相关的有哪些最好的实践?

1、根据需要选择正确的集合类型。 比如,如果指定了大小,我们会选用Array而非ArrayList。如果我们想根据插入顺序遍历一个Map,我们需要使用TreeMap。如果我们不想重复,我们应该使用Set。
2、一些集合类允许指定初始容量,所以如果我们能够估计到存储元素的数量,我们可以使用它,就避免了重新哈希或大小调整
3、基于接口编程,而非基于实现编程,它允许我们后来轻易地改变实现。
4、总是使用类型安全的泛型,避免在运行时出现ClassCastException。
5、使用JDK提供的不可变类作为Map的key,可以避免自己实现hashCode()和equals()。
6、尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护性。

48. TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

  • 比较元素

TreeSet:类实现Comparable接口!比较元素大小!
TreeMap:key实现Comparable接口!根据键进行比较!

  • Collections工具类中的sort()方法:

一个是要求传入的容器的元素实现Comparable接口!sort(list);
一个是第二个参数传入比较器!sort(list,compara()->{……})

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值