迭代器模式
问题思考
首先我们定义一个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 只会把这块缓冲区填满就返回,不会把整个文件塞给你。