迭代器模式-"我也想被增强for循环"

迭代器模式

问题思考

首先我们定义一个User类

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
}

我们写一个增强for循环:

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        User jack = new User("杰克",20);
        User lucy = new User("露西", 19);
        List<User> userList = new ArrayList<>();
        userList.add(jack);
        userList.add(lucy);
        for(User user:userList){
            System.out.println(user);
        }
    }
}

可以正常输出,那么问题1来了

问题1:为啥userList可以被增强for循环?

我们的User对象没法被增强for循环,比如写下面的代码就报错

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        User jack = new User("杰克",20);
        for(Object object:jack){//编译报错
            System.out.println(object);
        }
    }
}

为啥有的对象就可以被增强for循环,有的对象就不可以

从面向对象的角度: 显然是接口,有此能力的类继承该接口,没有此能力的类不继承

但是是什么接口呢?

我们看能被增强for循环的字节码文件:

可以看到有三个关键的方法:Iterator()hasNext()next()

所以我们的增强for循环,最后被编译之后是这样的:

java 复制代码
Iterator<User> iterator = userList.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

于是我们顺腾摸瓜,会看到Collection接口继承了Iterable接口

由此,我们便明白了,如果想要能够被迭代,就要继承这个Iterable接口,然后重写这个方法

我们让User类来继承这个接口

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Iterator;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Iterable<String> {
    private String name;
    private Integer age;

    //需要返回一个Iterator,我们来实现这个类
    @Override
    public Iterator<String> iterator() {
        
        
    }
}

完整实现

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Iterator;
import java.util.NoSuchElementException;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Iterable<String> {
    private String name;
    private Integer age;

    class UserIterator implements Iterator<String>{
        int cursor = 2;

        @Override
        public boolean hasNext() {
            return cursor>0;
        }

        @Override
        public String next() {
            cursor--;
            if(cursor == 1){
                return User.this.name;
            }else if(cursor == 0){
                return User.this.age+"";
            }

            throw new NoSuchElementException();//Iterator源码注释说如果没有就抛出这个异常
        }
    }

    //需要返回一个Iterator,我们来实现这个类
    @Override
    public Iterator<String> iterator() {
        return new UserIterator();
    }
}

至此,我们的User类也成功完成迭代

java 复制代码
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        User jack = new User("杰克",20);
        for(String str:jack){
            System.out.println(str);//可以正常输出
        }
    }
}

问题2:异常的报错

看下面的代码,你认为是否会报错?

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        User jack = new User("杰克",20);
        User lucy = new User("露西", 19);
        List<User> userList = new ArrayList<>();
        userList.add(jack);
        userList.add(lucy);
        for(User user:userList){
            if(user.getAge()==19){
                userList.add(new User("艺艺生辉",21));
            }
        }
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

答案是报错了,报错信息如下:

php 复制代码
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
	at com.hao.iterator.Main.main(Main.java:13)

那原因是为啥呢?为啥在迭代器中新增一个User就会报错?

其实我们知道,增强for循环本质上就是迭代器,所以我们把增强for循环的代码变为迭代器的代码

java 复制代码
Iterator<User> iterator = userList.iterator();
while (iterator.hasNext()){
    User user = iterator.next();
    if(user.getAge()==19){
        userList.add(new User("艺艺生辉",21));
    }
}

问题就出在这里iterator.next()方法里面

我们发现,iterator.next()方法有这样一个校验

java 复制代码
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

而我们在获取iterator的时候,会相当于记录一个版本号

而我们在add的时候,会修改这个值

所以最终原因:next()方法校验不通过,next()Iterator类的一个方法,这个类每次获取时记录一个快照,新增时修改了这个值,导致,校验不通过。

实践

实现一个可以从文件批量读取User类,然后进行处理

文件的样子:

txt 复制代码
[张三,18]
[李四,25]
[王五,32]
[赵六,41]
[刘七,28]
[陈八,35]
[杨九,22]
[黄十,45]

实现一个UserFile类:

java 复制代码
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Iterator;
import java.util.List;

public class UserFile implements Iterable<User>{

    private File file;
    public UserFile(File file){
        this.file = file;
    }

    @Override
    public Iterator<User> iterator() {
        return new UserItr();
    }

    class UserItr implements Iterator<User>{
        private List<User> userList = loadFilesToList();
        int cursor = 0;

        public User parseLine(String line){
            String[] split = line.substring(1, line.length() - 1).split(",");
            return new User(split[0], Integer.parseInt(split[1]));
        }

        public List<User> loadFilesToList()  {
            try {
                return Files.readAllLines(file.toPath()).stream()
                        .map(this::parseLine)
                        .toList();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }


        @Override
        public boolean hasNext() {
            if(cursor>=userList.size()){
                throw new NoSuchElementException();
            }
            return cursor<userList.size();
        }

        @Override
        public User next() {
            return userList.get(cursor++);
        }

    }
}

现在就可以直接使用啦

java 复制代码
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        File file = new File("src/main/resources/users.txt");
        UserFile userFile = new UserFile(file);
        for(User user:userFile){
            System.out.println(user); //可以正常输出
        }

    }
}

优化

因为如果文件太大,一次性加载到内存中会发生OOM,所以我们使用BufferedReader进行优化

java 复制代码
import java.io.*;
import java.nio.file.Files;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class UserFile implements Iterable<User>{

    private File file;
    public UserFile(File file){
        this.file = file;
    }

    @Override
    public Iterator<User> iterator() {
        return new UserItr();
    }

    class UserItr implements Iterator<User>{
        private BufferedReader reader;
        private String nextLine;

        UserItr() {
            try {
                this.reader = new BufferedReader(new FileReader(file));
                this.nextLine = reader.readLine(); //预读第一行
            }catch (IOException e){
                e.printStackTrace();
            }

        }

        public User parseLine(String line){
            String[] split = line.substring(1, line.length() - 1).split(",");
            return new User(split[0], Integer.parseInt(split[1]));
        }




        @Override
        public boolean hasNext() {
            return nextLine != null;
        }

        @Override
        public User next() {
            if(nextLine == null){
                throw new NoSuchElementException();
            }
            User user = parseLine(nextLine);
            try {
                nextLine = reader.readLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return user;
        }

    }
}

BufferedReader 底层调用的是操作系统的 read() 系统调用,这个调用接收一个固定大小的缓冲区(BufferedReader 默认 8192字节,OS 只会把这块缓冲区填满就返回,不会把整个文件塞给你。

相关推荐
咖啡八杯2 天前
GoF设计模式——策略模式
java·后端·spring·设计模式
槑有老呆3 天前
别再手搓 Prompt 了,那个叫"手动挡循环"
设计模式
用户6919026813394 天前
Vibe Coding 开发项目的基本范式
人工智能·设计模式·代码规范
怕浪猫5 天前
领域特定语言(Domain-Specific Language, DSL)
设计模式·程序员·架构
Larcher7 天前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式
咖啡八杯8 天前
GoF设计模式——享元模式
java·spring·设计模式·享元模式
:mnong8 天前
学习创建结构行为设计模式
设计模式