阿里Java开发手册剖析:
-
6.5【强制】ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException
-
6.8【强制】在subList场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationException
-
6.9【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
-
6.11【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove
-
6.14【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式
-
6.16【推荐】集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。 说明:菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。
【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
toArray()
和toArray(T[] array)
,建议使用有参数的。
直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现ClassCastException 错误。
反例demo:
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add("4");
arrayList.add("5");
//默认返回的是Object类型的数组
//Object[] objects = arrayList.toArray();
String[] objects = (String[]) arrayList.toArray();
}
运行结果如下:
主要原因还是集合中的数据类型比如String,是作为ArrayList的泛型存在:
public class ArrayList<E>
。泛型在运行时会被擦除。所以运行时ArrayList转换成数组时,是不知道其中元素的具体类型,所以只能是Object了。
正确用法:
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add("4");
arrayList.add("5");
String[] ints = {};
String[] strings = arrayList.toArray(ints);
for (String string : strings) {
System.out.println(string);
}
}
输出如下:
1
2
3
4
5
Process finished with exit code 0
为什么传入个带泛型的数组就能得到正确类型呢? 首先数组的泛型在运行时不会被擦除。
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
使用 toArray 带参方法,数组空间大小的 length最好是0
- 等于 0,动态创建与 size 相同的数组,性能最好。
- 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。
- 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与 2 相同。
- 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。
下面是toArray(T[] a)
的源码:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
当a.length<arrayList.size
时,直接使用了ArrayList的size,所以此时传入一个0<length<size
大小的数组a就是浪费空间。
如果如length==size
,那么多线程还是可能会重复情况2.即0<length<size
。
如果length>size
,就是浪费空间,还需要把a[size]=null
。又会有空指针风险。