RxJava如何获取缓存数据副本?避免缓存数据被订阅者修改
解决RxJava中replay多播后订阅者修改共享数据的问题
这个问题的核心在于 replay().autoConnect() 缓存的是对象引用,所有订阅者共享同一个原始数据实例——当某个订阅者修改对象内容时,其他订阅者自然会看到改动后的结果。要在只执行一次网络请求的前提下,给每个订阅者提供独立的缓存副本,我们只需要在多播后的Observable中添加一个深拷贝的逻辑即可。
解决方案思路
在replay().autoConnect()之后添加一个map操作符,为每个订阅者生成一份原始缓存数据的深拷贝。这样:
- 网络请求只会执行一次,结果被
replay缓存 - 每个订阅者订阅时,都会通过
map拿到独立的深拷贝,修改自己的副本不会影响缓存和其他订阅者
具体实现
1. 手动实现深拷贝(适合简单对象)
如果你的数据结构比较简单,可以手动编写拷贝逻辑:
首先,给Person类添加一个拷贝构造方法(方便快速创建新实例):
public class Person { String name; public Person(String name) { this.name = name; } // 拷贝构造方法,用于深拷贝 public Person(Person other) { this.name = other.name; } @Override public String toString() { return name; } }
然后,实现深拷贝方法并应用到Observable中:
private List<Person> getPersonList() { List<Person> persons = new ArrayList<>(); persons.add(new Person("Person A")); persons.add(new Person("Person B")); persons.add(new Person("Person C")); return persons; } // 深拷贝Person列表的方法 private List<Person> deepCopyPersonList(List<Person> original) { List<Person> copyList = new ArrayList<>(); for (Person p : original) { // 使用拷贝构造方法创建独立的Person实例 copyList.add(new Person(p)); } return copyList; } // 修改后的Observable,添加map统一处理深拷贝 Observable<List<Person>> netWorkCall = Observable.fromCallable(this::getPersonList) .replay() .autoConnect() .map(this::deepCopyPersonList);
2. 使用序列化工具做深拷贝(适合复杂对象)
如果你的数据结构复杂(比如有多层嵌套对象),手动写拷贝逻辑太繁琐,可以用Gson、Jackson等序列化工具来实现深拷贝:
// 引入Gson依赖后实现深拷贝 private List<Person> deepCopyPersonList(List<Person> original) { Gson gson = new Gson(); String jsonStr = gson.toJson(original); return gson.fromJson(jsonStr, new TypeToken<List<Person>>() {}.getType()); }
修改后测试效果
运行你的订阅代码,第三个订阅者会输出期望的原始数据,而非被修改后的结果:
// 第一个订阅 netWorkCall.subscribe(persons -> { for (Person person : persons) { System.out.println(person); } // 输出: // Person A // Person B // Person C }); // 第二个订阅,执行修改操作 netWorkCall .flatMap(persons -> Observable.fromCallable(() -> persons)) .subscribe(persons -> { for (int i = 0; i < persons.size(); i++) { Person person = persons.get(i); person.name = "Person " + i; System.out.println(person); } // 输出: // Person 0 // Person 1 // Person 2 }); // 第三个订阅,拿到独立的原始拷贝 netWorkCall.subscribe(persons -> { for (Person person : persons) { System.out.println(person); } // 输出(符合期望): // Person A // Person B // Person C });
关键注意点
- 不要把
map放在replay之前:否则replay缓存的是拷贝后的对象,所有订阅者还是会共享同一个拷贝实例,问题依旧存在。 - 深拷贝粒度要足够:如果
Person类还有嵌套的可变对象,也要保证这些嵌套对象被正确拷贝,避免引用共享。
内容的提问来源于stack exchange,提问作者user4260260
相关产品推荐
相关产品推荐

