Rx-java pass by reference or pass by value?

2019-08-11 02:23发布

问题:

In java methods everything is passed-by-value so i can change the object attributes passed to the method and expect that the original object attributes are changed. but in this method i get different result:

I have this method:

public Observable<Menu> makeMenu(Menu menu, NumberSettingChanges.MenuChanges changes) {
    // Start flow with added and edited extensions
    return Observable.from(changes.added.entrySet())
            .mergeWith(Observable.from(changes.edited.entrySet()))
                    //Upload announcement voices or do nothing if extension is not an announcement
            .flatMap(e -> {
                        if (AppTypeContract.APP_TYPE_ANNOUNCEMENT.equals(e.getValue().type)) {
                            return mMediaManager.uploadAsync(e.getValue().config.localPrompt)
                                    .doOnNext(response -> {
                                        //Update extension prompt with the storage path.
                                        menu.config.extensions.get(e.getKey()).config.prompt = response.mPath;
                                        menu.config.extensions.get(e.getKey()).config.localPrompt = "";
                                    })
                                    .flatMap(response -> Observable.just(e));
                        } else {
                            return Observable.just(e);
                        }
                    }
            )
}

and i manipulate menu attributes in the flatmap:

menu.config.extensions.get(e.getKey()).config.localPrompt = "";

I call the method in the same class:

public Observable<NumberSetting> saveSettings(NumberSetting o, NumberSetting n) {
    NumberSettingChanges changes = compareNumberSetting(o, n);

    return makeMenu(n.day, changes.day)
            .mergeWith(makeMenu(n.night, changes.night));
    }

and finally:

saveSettings(ns, mNumberSettingNew).subscribe();

What i expect is that the mNumberSettingNew.menu.config.extensions.get(e.getKey()).config.prompt is changed but no change is happening after this call and the mNumberSettingNew has no change at all.

Note that i am sure that changing prompt line is done in the debug.

回答1:

I don't think I could explain Java's parameter semantics any better than (or even half as good as) the link you referenced in your first paragraph so I won't try. The main point is: Everything in Java is passed by value (i. e. copied) but with objects what is copied is not the object itself but the reference to the object. So in other words the reference is passed by value.

So with respect to your particular problem: Yes, if you pass a reference to a mutable object to some rx-java code that reference will point to the same instance of the object. If you mutate the instance then the caller code will also be able to see the changes because they were made on the same instance. That's because rx-java is still only Java and cannot change the language semantics on that level.

Without seeing the whole code I am unsure what could be the problem here... When are you checking whether mNumberSettingsNew actually has the changes you were making in your doOnNext? If you check that immediately after saveSettings(ns, mNumberSettingNew).subscribe(); your uploadAsync may not have returned yet. You could try adding an actual Subscriber in your subscribe and check the result there.

On a more general note, I think you should try to avoid side-effects like this as much as you can when using rx-java. Your case - taking an input object, applying a set of (possibly asynchronous) changes to that object, and waiting for the changed output object - is a bit tricky, but I think it could be done with scan. Maybe something vaguely like this:

Observable.from(changes.added.entrySet())
    .mergeWith(Observable.from(changes.edited.entrySet()))
    .scan(menuBeforeAnyChanges, new Func2<Menu, Change, Menu>() {

        public Menu call(final Menu previousVersionOfTheMenu, final Change nextChange) {
            // since I don't know of a version of scan that can return 
            // an Observable you would I think you would have to adapt
            // your code in here to be fully synchronous - but of
            // course the scan itself could run asynchronously
            final newVersionOfTheMenu = previousVersionOfTheMenu.applyChange(nextChange);
            return newVersionOfTheMenu;
        }
     )

This would take the original Version of the menu, consecutively apply all the changes from added and edited and /emit/ every updated version of menu. So you would not have any side effects but simply subscribe to that observable with a Subscriber<Menu> and then take the last() Menu and that would be the one with all changes applied.

EDIT: Oh, I just saw that there is another method called reduce that does just that: first scan and then last or takeLast.