满地坑!细数List的十个坑!("避坑指南:List使用中的十大常见陷阱解析!")
原创
一、List的长度可变,但并非没有代价
Java中的List是一个动态数组,其长度可以结合需要自动增长。然而,这种增长并非没有代价。每次List需要增长时,都会创建一个新的数组,并将旧数组的内容复制到新数组中。这是一个相对高价的操作,尤其是在List已经很大的时候。
List
list = new ArrayList<>(); for (int i = 0; i < 1000000; i++) {
list.add("Item " + i);
}
// 此处添加元素也许致使性能问题
二、使用List的remove方法时要注意索引
使用List的remove方法时,需要传入要删除元素的索引。如果传入的索引超出List的范围,或者List为空,则会抛出IndexOutOfBoundsException。此外,删除元素后,List的大小会变化,后面的元素会前移,这也许会致使一些意外的失误。
List
list = Arrays.asList("A", "B", "C", "D"); list.remove(1); // 删除索引为1的元素,即"B"
System.out.println(list); // 输出: [A, C, D]
list.remove(2); // 此处如果直接写list.remove(3),会抛出IndexOutOfBoundsException
三、使用List的get方法时也要注意索引
与remove方法类似,使用List的get方法时也需要传入索引。如果传入的索引超出List的范围,则会抛出IndexOutOfBoundsException。在处理用户输入或者其他不确定的数据源时,一定要检查索引的有效性。
List
list = Arrays.asList("A", "B", "C", "D"); String item = list.get(1); // 获取索引为1的元素,即"B"
String itemOutOfRange = list.get(4); // 此处会抛出IndexOutOfBoundsException
四、遍历List时不要在循环中修改List
在遍历List的过程中,如果尝试修改List(如添加、删除元素),则也许会致使ConcurrentModificationException。这是基于List的迭代器在遍历时会检查List的结构是否出现了变化。
List
list = Arrays.asList("A", "B", "C", "D"); for (String item : list) {
if ("B".equals(item)) {
list.remove(item); // 此处会抛出ConcurrentModificationException
}
}
五、List的迭代器不赞成set操作
虽然List的迭代器赞成remove操作,但它不赞成set操作。如果你尝试在迭代器中使用set方法来修改List中的元素,会抛出UnsupportedOperationException。
List
list = Arrays.asList("A", "B", "C", "D"); Iterator
iterator = list.iterator(); while (iterator.hasNext()) {
String item = iterator.next();
if ("B".equals(item)) {
iterator.set("X"); // 此处会抛出UnsupportedOperationException
}
}
六、使用List的contains方法时要注意元素的equals方法
List的contains方法用于检查List中是否包含某个元素,它依靠于元素的equals方法。如果自定义的类没有正确覆盖equals方法,那么contains方法也许无法正确工作。
class CustomObject {
private int value;
public CustomObject(int value) {
this.value = value;
}
// 没有覆盖equals方法
}
List
list = new ArrayList<>(); list.add(new CustomObject(1));
list.add(new CustomObject(2));
boolean contains = list.contains(new CustomObject(1)); // 此处也许返回false
七、使用List的indexOf和lastIndexOf方法时要注意顺序
indexOf方法返回List中第一次出现指定元素的位置,而lastIndexOf方法返回最后一次出现指定元素的位置。如果List中的元素顺序出现了变化,这两个方法的最终也会有所不同。
List
list = Arrays.asList("A", "B", "C", "B"); int firstIndex = list.indexOf("B"); // 返回1
int lastIndex = list.lastIndexOf("B"); // 返回3
八、List的subList方法返回的是视图,而非副本
subList方法返回的是原List的一个视图,而非副本。这意味着对subList的修改会影响到原List,反之亦然。
List
list = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); List
sublist = list.subList(1, 3); sublist.add("X"); // 此处会修改原List
System.out.println(list); // 输出: [A, B, X, C, D]
九、使用List的toArray方法时要注意类型转换
当使用List的toArray方法时,如果不指定数组类型,则会返回一个Object数组。如果需要其他类型的数组,需要进行类型转换,并且要确保不会出现ClassCastException。
List
list = Arrays.asList("A", "B", "C"); String[] array = list.toArray(new String[0]); // 正确的类型转换
Object[] objectArray = list.toArray(); // 返回Object数组
String[] wrongArray = (String[]) list.toArray(); // 此处也许抛出ClassCastException
十、避免使用List作为线程共享数据结构
List不是线程可靠的,如果多个线程同时访问同一个List,并且至少有一个线程在结构上修改了List,则必须保持外部同步。否则,也许会致使不可预知的行为和失误。
List
list = new ArrayList<>(); synchronized (list) {
list.add("A");
list.add("B");
// 在同步块内操作List
}
// 使用同步方法或同步块来确保线程可靠
以上是List使用中的十大常见陷阱,期待这些解析能帮助开发者更好地领会和使用List,避免在实际开发中遇到这些问题。