You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:40:10