Java List踩坑记录

本文记录了在使用Java List时遇到的两个常见问题:List中所有元素指向同一内存地址,导致数据错误;以及List中包含null元素,特别是在Mybatis查询后的处理。此外,还简单介绍了List接口、ArrayList、LinkedList和Vector的区别。在处理List时,应避免使用for循环直接删除元素,推荐使用Iterator。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

踩坑一 List中所有元素指向同一个地址内存

ArrayList中的每一个元素存储的实际上是对象引用(之前在公司写代码的时候,做过类似下面的事),假如按照下面的方式使用ArrayList,则最后list中存储的元素都相同且都是最后一个元素,原因是list中所有的元素都指向同一块内存。

@Test
public void test1(){
    List<Person> list = new ArrayList<>(16);
    Person p = new Person();
    for(int i = 0; i < 10; i++){
        p.setName("test"+i);
        p.setAge(i);
        list.add(p);
    }
    System.out.println(list);
}

结果所有list中的元素都指向同一个Person实例

[Person{name='test9', age=9},  Person{name='test9', age=9}, ...,Person{name='test9', age=9}, Person{name='test9', age=9}]
踩坑二 List中存储了一个元素null

这个坑出现了两次,一次是使用stream之后collect转为list,但是结果为List[null],另外一次就是使用Mybatis查询数据库返回的结果为List[null];

  1. 使用stream之后collect转为list结果为List[null]
    stream通过流映射出list中为null的值,转为为list,此时就会出现List中有null的值。
    @Test
    public void testStream(){
        List<Person> list = Arrays.asList(new Person("cc"), new Person("abc"));
        List<Long> collect = list.stream().map(Person::getAge).collect(Collectors.toList());
        System.out.println("stream操作之后:"+collect);
        collect.removeAll(Collections.singleton(null));
        System.out.println("removeAll之后:"+collect);
    }
    
    上面代码执行结果:
    stream操作之后:[null, null]
    removeAll之后:[]
  2. 数据库返回的结果为List[null]
    SELECT
        NULL AS a.col1, 
        b.*
    FROM
        a LEFT JOIN b ON a.id = b.id
    where a.col2= 'AAA';
    
    假如此时a表查到了数据,而b表没有查询到数据,那么此时返回的是每一行是[null],具体行数则由a的行数来决定;如果使用的是mybatis查询,返回的结果应该是一个list,只不过这个list里面所有的元素都是null。

对于上面说的两种List[null]情况,如果是不需要list中有null值得话,都可以借助collect.removeAll(Collections.singleton(null));来去除list中的null。

List接口

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

上面这段引用就是jdk8对List的定义-一个有序的集合。

ArrayList、LinkedList、Vector的异同
  • 三个实现类都存储有序的、可重复的数据
  • ArrayList相当于数据结构中的线性表,是List接口的主要实现类;具有线程不安全、效率高的特点;底层使用Object[] elementData存储数据。
  • LinkedList相当于数据结构中的链表;处理插入、删除操作时,效率比ArrayList高;底层使用双向链表存储数据。
  • Vector是List接口的比较老的实现类;线程安全但是效率低;底层使用Object[] elementData存储数据。

由于List下面的三个实现类都比较简单,这里就不做过多的赘述。主要是注意一下如果遍历的过程中要对List进行删除操作的时候最好是使用Iterator,不要使用for(不管是普通for还是增强for),会出现错误,具体错误如下:

@Test
public void test4(){
    List<Employee> list = new ArrayList<>(16);
    for(int i = 0; i < 5; i++){
        list.add(new Employee("test",i,i));
    }

    for(int i = 0; i < list.size(); i++){
        Employee employee = list.get(i);
        if(employee.age == employee.salary) {
            list.remove(i);
        }
    }
    for(Employee employee:list){
        System.out.println(employee);
    }
    /**
     * 打印结果:
     * Employee{name='test', age=1, salary=1}
     * Employee{name='test', age=3, salary=3}
     */
}

上面的代码使用普通for循环进行删除操作,按照我们的本意是会将所有元素都删除,但是结果还剩下2个,原因是因为每次删除i位置的元素后,List中i之后的元素向前走一步,然后执行了一次i++,于是乎跳过了一个元素,慢慢的会积累多个出来。再看下增强for:

@Test
public void test4(){
    List<Employee> list = new ArrayList<>(16);
    for(int i = 0; i < 5; i++){
        list.add(new Employee("test",i,i));
    }
    for(Employee employee:list){
        if(employee.age == employee.salary){
            if(list.remove(employee)){
                System.out.println("remove success");
            }
        }
    }
    for(Employee employee:list){
        System.out.println(employee);
    }
    /**
     * 打印结果:
     * run equals()....
     * remove success
     * 
     * java.util.ConcurrentModificationException
     * 	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
     * 	at java.util.ArrayList$Itr.next(ArrayList.java:857)
     * 	at com.chapter12_collection.CollectionTest.test4(CollectionTest.java:70)
     */
}

发现增强for的确删除一个元素成功了,但是删除第二个元素的时候报错ConcurrentModificationException,于是乎增强for中删除元素也是不可行的。那我们再来看下正确的做法:

@Test
public void test4(){
    List<Employee> list = new ArrayList<>(16);
    for(int i = 0; i < 5; i++){
        list.add(new Employee("test",i,i));
    }
    Iterator<Employee> iterator = list.iterator();
    while (iterator.hasNext()){
        Employee employee = iterator.next();
        if(employee.age == employee.salary){
            iterator.remove();
        }
    }
	/**
     * 打印结果为空
     */
}

@Test
public void test5(){
    List<Employee> list = new ArrayList<>(16);
    for(int i = 0; i < 5; i++){
        list.add(new Employee("test"+i,i*2,i*3));
    }
    Iterator<Employee> iterator = list.iterator();
    while (iterator.hasNext()){
        Employee employee = iterator.next();
        if(employee.age == 0){
            iterator.remove();
            continue;
        }
        System.out.println(employee);
    }
    /**
     * 打印结果:
     * Employee{name='test1', age=2, salary=3}
     * Employee{name='test2', age=4, salary=6}
     * Employee{name='test3', age=6, salary=9}
     * Employee{name='test4', age=8, salary=12}
     */
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值