文章目录
- 1. 【强制】 关于 hashCode 和 equals 的处理,遵循如下规则:
- 2. 【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException异常
- 3. 【强制】在 subList 场景中, 高度注意对原集合元素的增加或删除, 均会导致子列表的遍历、
- 4. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
- 6. 【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
- 7. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁
- 8. 【强制】 在 JDK7 版本及以上, Comparator 实现类要满足如下三个条件,不然 Arrays.sort,
- 9. 【推荐】 集合泛型定义时, 在 JDK7 及以上,使用 diamond 语法或全省略。
- 10. 【推荐】集合初始化时, 指定集合初始值大小。
- 11. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
- 13.【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
- 14.【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
1. 【强制】 关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的
对象必须重写这两个方法。
3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
说明: String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象
作为 key 来使用。
解析:
实例程序
public static void main(String[] args) {
HashSet a = new HashSet();
a.add("test");
a.add("test");
a.add("test");
a.add("test");
}
在Set中有这样的源码
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
可见一直在判断e.hash和key是否Equals才考虑是否加入set,所以看到重写的重要性
2. 【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException异常
即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
说明: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList
的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
3. 【强制】在 subList 场景中, 高度注意对原集合元素的增加或删除, 均会导致子列表的遍历、
增加、删除产生 ConcurrentModificationException 异常
我们来查看一下ArrayList.subList的源码
实例程序
public static void main(String[] args) {
ArrayList array = new ArrayList();
array.add(1);
array.add(2);
array.add(3);
array.add(4);
List s =array.subList(0, 3);
System.out.println(s);
}
源码
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
查看继承机构确实SubList是ArrayList的内部类,必须依赖外部接口的实现。
但他返回的是一个AbstractList对象,ArrayList也是返回了一个public class ArrayList extends AbstractList对象
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
这算是一个适配器模式,要注意的是
SubList也实现了add,remove方法
查看add方法
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
checkForComonfication()会检查是否有别的线程修改了内容
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
ArrayList的add和remove方法
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
modCount 是检查是否有其他线程同时修改了里面的内容
所以要subList时, 不能够在原来的ArrayList随意的remove和add
详细阅读:https://www.jianshu.com/p/c5b52927a61a
4. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
正解
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
下一个
6. 【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
我也不会
下一个
7. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁
实例程序
public class MainTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("1")) {
iterator.remove();
}
}
list.add("1");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
}
}
查看字节码发现
上面调用的是iterator.remove()方法,foreach下面调用的是List.remove方法
我们来看看里面的内容
List.remove方法里有
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("1")) {
iterator.remove();
}
}
list.add("1");
Iterator<String> iterator2 = list.iterator();
while (iterator2.hasNext()) {
String item = iterator.next();
if (item.equals("1")) {
list.remove(item); //这里变化了
}
}
}
iterator里有一个操作数记录器modCount=0;
而list也有一个操作记录数modCount=0; list.remove(item)中并没有修改到Iteartor的修改书modCount,所以Iterator以为是并发修改了数组里的内容,所以发生了
Exception in thread "main" java.util.ConcurrentModificationException
下一个
8. 【强制】 在 JDK7 版本及以上, Comparator 实现类要满足如下三个条件,不然 Arrays.sort,
Collections.sort 会报 IllegalArgumentException 异常。Collections.sort 会报 IllegalArgumentException 异常。
说明: 三个条件如下
1) x, y 的比较结果和 y, x 的比较结果相反。
2) x>y, y>z, 则 x>z。
3) x=y, 则 x, z 比较结果和 y, z 比较结果相同
这个里面的内容比较复杂,我没看懂。。
实践程序也正常
public class MainTest {
public static void main(String[] args) {
ArrayList<Student> a = new ArrayList<Student>();
Student s = new Student(1,"david");
Student s2 = new Student(1,"s2");
Student s3 = new Student(2,"s3");
Student s4 = new Student(3,"s4");
Student s5 = new Student(4,"s5");
Student s6 = new Student(5,"s6");
a.add(s);
a.add(s2);
a.add(s3);
a.add(s4);
a.add(s5);
a.add(s6);
Collections.sort(a,new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
});
System.out.println(a);
}
}
class Student {
Integer id;
String name;
public Student(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
9. 【推荐】 集合泛型定义时, 在 JDK7 及以上,使用 diamond 语法或全省略。
说明: 菱形泛型,即 diamond, 直接使用<>来指代前边已经指定的类型。
正例:
// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList users = new ArrayList(10);
个人解析:
感觉还是写全语法比较好
下一个
10. 【推荐】集合初始化时, 指定集合初始值大小。
说明: HashMap 使用 HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader
factor) 默认为 0.75, 如果暂时无法确定初始值大小,请设置为 16(即默认值) 。
反例: HashMap 需要放置 1024 个元素, 由于没有设置容量初始大小,随着元素不断增加,容
量 7 次被迫扩大, resize 需要重建 hash 表,严重影响性能。
已下降为解析转载来自:
https://cloud.tencent.com/info/9bdbd5c9a03e27a5b6005c005d854829.html
实例程序
int aHundredMillion = 10000000;
Map<Integer, Integer> map = new HashMap<>();
long s1 = System.currentTimeMillis();
for (int i = 0; i < aHundredMillion; i++) {
map.put(i, i);
}
long s2 = System.currentTimeMillis();
System.out.println("未初始化容量,耗时 : " + (s2 - s1));
Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2);
long s5 = System.currentTimeMillis();
for (int i = 0; i < aHundredMillion; i++) {
map1.put(i, i);
}
long s6 = System.currentTimeMillis();
System.out.println("初始化容量5000000,耗时 : " + (s6 - s5));
Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion);
long s3 = System.currentTimeMillis();
for (int i = 0; i < aHundredMillion; i++) {
map2.put(i, i);
}
long s4 = System.currentTimeMillis();
System.out.println("初始化容量为10000000,耗时 : " + (s4 - s3));
查看源码发现
为什么是7次?
他的初始容量是16,每次增大两倍并且重组hashMap
所以16,32,64,128,256,512,1024 才能够装1024个数据,但重组了7次浪费了很多的效率
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 判断是否超过最大容量值
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 容量扩大为原来的两倍,oldCap大于等于16
newThr = oldThr << 1; // 双倍
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // 当没设置值时初始化变量
newCap = DEFAULT_INITIAL_CAPACITY; //默认值是16,下面是16*0.75=12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
....
下一个
11. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
说明: keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出
key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效
率更高。如果是 JDK8,使用 Map.foreach 方法。
正例: values()返回的是 V 值集合,是一个 list 集合对象; keySet()返回的是 K 值集合,是
一个 Set 集合对象; entrySet()返回的是 K-V 值组合集合。
参考了文章https://blog.csdn.net/chajinglong/article/details/79194967
实例程序
Map map = new HashMap();
map.put("1", "david");
map.put("2", "way");
map.put("3", "test");
map.put("4", "hehe");
map.put("5", "bgg");
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
System.out.println("key=");
}
Map map2 = new HashMap();
Iterator iter2 = map2.keySet().iterator();
while (iter2.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
12.【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
探讨一下什么是线程安全与线程不安全
实验程序
import java.util.Hashtable;
public class MainTest{
static int i=0;
public static void main(String[] args) {
Hashtable a = new Hashtable();
a.put("a", "a");
}
}
查看源码后发现
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
同理
ConcurrentHashMap
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
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
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
13.【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
说明: 有序性是指遍历的结果是按某种比较规则依次排列的。 稳定性指集合每次遍历的元素次
序是一定的。 如: ArrayList 是 order/unsort; HashMap 是 unorder/unsort; TreeSet 是
order/sort。
这个我没看懂
14.【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
contains 方法进行遍历、对比、 去重操作。
这个不太需要解析,但提供一个代码说明
List list = new ArrayList(set);
Set set = new HashSet(list);