集合遍历中删除行不行

本文探讨了在Java中遍历List并删除元素时的常见陷阱。通过具体示例,展示了不同遍历方式下的行为差异,包括使用增强for循环和传统for循环时,如何避免并发修改异常,并解释了其背后的原因。

package 算法;

import java.util.ArrayList;
import java.util.List;

public class 集合遍历中删除行不行 {
    /**
     * 面试中会问到list遍历能不能使用remove方法,下面就是遇到指定的数就删除
     * 
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<Integer> list=new ArrayList<Integer>();
        List<A> list2=new ArrayList<A>();
        list2.add(new A("111"));
        list2.add(new A("112"));
        list2.add(new A("113"));
        list2.add(new A("114"));
        list.add(1);
        list.add(3);
        list.add(5);
        list.add(9);
        for (int i = 0; i < list2.size(); i++) {
            if(i==2){
                list2.remove(i);
            }
            System.out.println(i);
        }
        for (Integer integer : list) {
            if(integer==5){
                list.remove(integer);
            }
            System.out.println(integer);
        }
        //正常
//        for(int i=0;i<list.size();i++){
//            System.out.println("前:"+list.get(i));
//            if(list.get(i)==5){
//                list.remove(i);
//            }
//            System.out.println("后:"+list.get(i));
//        }
//        //把i<4,虽然长度都是4但是会报下表越界
//        for(int i=0;i<4;i++){
//            System.out.println("前:"+list.get(i));
//            if(list.get(i)==5){
//                list.remove(i);
//            }
//            System.out.println("后:"+list.get(i));
//        }
//        
//    //--------总结上面两个可以得出,for(int i=0;i<list.size();i++)这种写法每次遍历都会重新计算list.size()的长度
//        
//        for(int i=0;i<list.size();i++){
//            System.out.println("前:"+list.get(i));
//            if(list.get(i)==9){
//                list.remove(i);
//            }
//            System.out.println("后:"+list.get(i));
//        }
    //--------总结当删除的是最后一个元素时,后面一个输出语句会报错,因为是先删除的,再次get(i)的i还是原先的长度,但是现在变短了。
        
    }
    
}
class A{
    public String aa;

    public A(String aa) {
        super();
        this.aa = aa;
    }
    
}

<think>我们有一个需求:使用 Kotlin 的 `forEachIndexed` 方法将集合中前两条元素调换位置。 注意:`forEachIndexed` 通常用于遍历执行操作(副作用),并不直接返回新集合。但我们可以通过修改一个可变的集合来实现元素调换。 思路: 1. 我们需要一个可变的集合(例如 MutableList),因为不可变集合(如 List)不能直接修改元素。 2. 遍历集合时,我们只需要处理前两个元素(索引0和1)。因此,我们在遍历到索引0时记录第一个元素,然后在索引1时,将索引0位置的元素替换为第二个元素,再将索引1位置的元素替换为之前记录的第一个元素。 3. 注意:我们不能在遍历过程中直接修改集合(在修改后继续遍历可能会引起并发修改异常?)。但实际上,在 Kotlin 的 forEachIndexed 中修改同一个可变集合是允许的,只要我们不改变集合的结构(如添加或删除元素),而只是修改元素的值。但是,调换两个元素只涉及修改元素值,不改变集合大小,所以是安全的。 然而,我们需要小心:在遍历过程中修改元素,我们可能会覆盖掉需要的数据。因此,我们需要先保存第一个元素的值。 步骤: 假设我们有一个可变的列表:`val list = mutableListOf("A", "B", "C", "D")` 我们想要交换前两个元素,使其变成 ["B", "A", "C", "D"] 我们可以: - 当 index=0 时,记录第一个元素(记为temp)。 - 当 index=1 时,将第一个位置(index0)设置为当前元素(即第二个元素),然后将第二个位置(index1)设置为之前记录的temp。 但是注意:在遍历到index0时,我们记录第一个元素,然后在遍历到index1时进行交换。 然而,`forEachIndexed` 会按顺序遍历每个元素,所以我们可以这样操作。 另一种做法(更简单):我们实际上不需要遍历整个集合,只需要处理前两个元素。但是 `forEachIndexed` 会遍历所有元素,所以我们需要在遍历到索引>=2时跳过。 具体代码: ```kotlin fun main() { val list = mutableListOf("A", "B", "C", "D") var temp: String? = null // 用于暂存第一个元素 list.forEachIndexed { index, value -> if (index == 0) { temp = value // 记录第一个元素 } else if (index == 1) { // 将第一个位置设置为当前第二个元素 list[0] = value // 将第二个位置设置为之前记录的第一个元素 list[1] = temp!! // 这里使用非空断言,因为我们知道temp在index0已经被赋值 } // 其他索引不做处理 } println(list) // 期望输出 [B, A, C, D] } ``` 但是,这种方法虽然有效,但有两个注意事项: 1. 我们使用了可空类型和断言(!!),因为temp在初始化时为null,但在index0时被赋值。我们可以通过初始化temp为第一个元素来避免?不行,因为我们需要在遍历index0时记录,然后到index1时使用。 2. 我们遍历了整个列表,但只处理了前两个元素,对于大列表来说,这可能有点浪费。不过,如果列表不大,这没有问题。 另外,注意:这种方法只能用于可变的集合(MutableList)。如果原始集合是不可变的,我们需要先创建一个可变的副本。 优化:我们其实可以不用`forEachIndexed`,而使用更直接的方式: list[0] = list[1].also { list[1] = list[0] } 但题目要求使用`forEachIndexed`,所以我们还是按照要求来。 另一种思路:我们也可以只遍历前两个元素?但是`forEachIndexed`会遍历整个集合。我们可以使用`take(2).forEachIndexed`,但注意这样操作的是原集合的一个子集(副本),不会修改原集合。 因此,我们上面的方法是符合要求的。 但是,我们也可以这样改进:在遍历到索引大于1时,我们可以不做任何事。代码已经这样做了。 为了更安全(避免空指针),我们可以将temp的类型设置为与列表元素相同,并初始化为null(需要一个可空类型)。然后我们在index0时赋值,在index1时使用,并确保只有在index1时使用temp,此时temp已经被赋值。所以是安全的。 但是,如果列表只有一个元素或为空呢?我们需要考虑边界情况。 修改后的代码: ```kotlin fun main() { val list = mutableListOf("A", "B") // 测试两个元素 // 如果列表元素少于两个,则不需要交换 if (list.size < 2) { println("列表元素少于2个,无法交换") return } var temp: String? = null // 或者使用list[0]的类型 list.forEachIndexed { index, value -> when (index) { 0 -> temp = value 1 -> { list[0] = value list[1] = temp!! } // else 可以什么都不做 } } println(list) // 输出 [B, A] } ``` 这样,对于元素不足两个的情况,我们提前处理了。 但是,请注意,如果列表很大,我们仍然会遍历整个列表,但除了前两个元素之外的操作都是空的,所以开销很小。 总结:使用`forEachIndexed`交换前两个元素,我们需要一个可变集合,并在遍历过程中记录第一个元素,然后在遍历到第二个元素时进行交换。 然而,使用`forEachIndexed`来交换前两个元素并不是最有效的方式,因为我们遍历了整个集合。但题目要求使用`forEachIndexed`,所以我们这样实现。 另一种写法(使用also操作符,避免临时变量temp): 我们可以不用显式的temp变量,而是利用also操作符来实现交换: 但是,在`forEachIndexed`中,我们无法避免使用临时变量,因为我们需要在两次操作之间记住第一个元素的值。 不过,我们可以这样: ```kotlin list.forEachIndexed { index, _ -> if (index == 0) { // 这里不能直接保存值,但我们可以通过list[0]来获取,但是后面会被覆盖 // 所以我们还是需要保存第一个元素的值 } } ``` 所以,我们还是需要temp变量。 因此,上面的代码是合理的。 现在,我们提供一个通用的扩展函数: ```kotlin fun <T> MutableList<T>.swapFirstTwo() { if (this.size < 2) return var temp: T = this[0] this[0] = this[1] this[1] = temp } ``` 这个扩展函数更直接高效。但是题目要求使用`forEachIndexed`,所以我们不用扩展函数的方式。 所以,最终我们还是使用前面遍历的方式。 但是注意:题目要求使用`forEachIndexed`,所以我们的代码必须使用它。 因此,我们提供的答案如下: </think>### 在 Kotlin 中使用 `forEachIndexed` 交换集合前两个元素的方法 #### 核心思路 `forEachIndexed` 本身是遍历操作,无法直接修改集合元素顺序。需要通过**临时变量存储值**并**操作可变集合**实现交换。关键点: 1. 使用 `MutableList`(可修改的集合) 2. 通过索引定位前两个元素(索引 0 和 1) 3. 使用临时变量暂存第一个元素的值 4. 在遍历到第二个元素时执行交换 #### 完整代码实现 ```kotlin fun main() { // 创建可变集合 val items = mutableListOf("Apple", "Banana", "Cherry", "Date") var firstItem: String? = null // 临时存储第一个元素 items.forEachIndexed { index, value -> when (index) { 0 -> firstItem = value // 存储第一个元素 1 -> { // 执行交换:将第一个元素赋给第二个位置 items[index] = firstItem!! // 将第二个元素赋给第一个位置 items[0] = value } // 其他元素无需操作 } } println(items) // 输出:[Banana, Apple, Cherry, Date] } ``` #### 关键步骤说明 1. **创建可变集合** ```kotlin val items = mutableListOf("Apple", "Banana", "Cherry", "Date") ``` 必须使用 `MutableList`,不可变集合(如 `listOf()` 创建的集合)无法修改元素[^1]。 2. **存储第一个元素** ```kotlin var firstItem: String? = null items.forEachIndexed { index, value -> if (index == 0) firstItem = value ``` 当遍历到索引 0 时,将第一个元素存入临时变量 `firstItem`[^3]。 3. **交换元素位置** ```kotlin 1 -> { items[index] = firstItem!! // 第二个位置存入第一个元素 items[0] = value // 第一个位置存入当前元素(原第二个元素) } ``` 当遍历到索引 1 时,执行交换操作: - 将临时变量值(原第一个元素)赋给索引 1 - 将当前元素(原第二个元素)赋给索引 0[^5] #### 边界情况处理 ```kotlin // 检查元素数量 if (items.size < 2) { println("集合元素不足2个,无法交换") return } ``` - 添加此检查可避免元素不足时的崩溃 - 空集合或单元素集合直接返回 #### 替代方案对比 | **方法** | **优点** | **缺点** | |------------------------|-----------------------------|-----------------------------| | `forEachIndexed` | 符合函数式风格 | 需手动处理边界条件 | | 直接下标操作 | 更简洁高效 | 不满足"必须用 forEachIndexed"的要求 | | `Collections.swap()` | 标准库方法,一行代码完成交换 | 无法在遍历中集成其他逻辑 | > 直接下标操作的推荐实现: > ```kotlin > if (items.size >= 2) { > items[0] = items[1].also { items[1] = items[0] } > } > ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老马识途2.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值