简体   繁体   中英

Returning an immutable reference to a mutable object in Java

Basically, I have a mutable object in Java and its state is both accessed and modified by several methods of a certain class. The data stored in the object is also used by another class, but the difference is that the second class should not be allowed to modify the data, just to use it.

In C++, I would normally just pass a const reference to the object at hand, but I realize that there is no direct equivalent in Java.

Just to clarify, the following code snippet shows what I have in mind:

public interface Figure
{
    void onSizeChanged(SizeInfo info);
    void draw();
}
public class FigureView extends View
{
    private Figure figure;
    private SizeInfo sizeInfo;

    ...

    public void onResize()
    {
        //Modify the state of sizeInfo, then pass it to the figure.
        figure.onSizeChanged(sizeInfo);
    }
}

Let's say that SizeInfo is a large object, so I don't want to make a memberwise copy, but just pass it by reference. The code above succeeds in allowing any Figure to access the data of the SizeInfo object, but it also allows the Figure to modify the object. Errors caused by such behavior may be hard to track, so I want to pass an 'immutable reference' to the SizeInfo .

The best solution I have found so far is to create an non-static inner class of SizeInfo which only consists of getters:

public class SizeInfo
{
    //These represent the large set of data inside the class.
    private long foo;
    private double bar;
    private Export mExport = new Export();

    //Setters (only available if you have a direct reference to SizeInfo):
    public void incrementFoo()
    {
        foo++;
    }
    public void setBar(double bar)
    {
        this.bar = bar;
    }

    //SizeInfo getters:
    public long getFoo()
    {
        return foo;
    }
    public double getBar()
    {
        return bar;
    }
    public double getBaz()
    {
        return bar * foo;
    }

    //A non-static inner class:
    public class Export
    {
        public long getFoo() { return foo; }
        public double getBar() { return bar; }
        public double getBaz() { return bar * foo; }
    }
    public Export export() { return mExport; }
}

With this code, you only have to change the method signature in Figure from onSizeChanged(SizeInfo) to onSizeChanged(SizeInfo.Export) and pass sizeInfo.export() to the method instead of sizeInfo to make it work as expected. This is very easy to use from the client side, but the code redundancy caused by having to repeat each getter twice is definitely not elegant. The alternative of placing the getters only in SizeInfo.Export and replacing each sizeInfo.getBaz() with sizeInfo.export().getBaz() is even worse. That is why I am looking for a more elegant approach.

I realize that this particular example might not be believable in terms of SizeInfo being too big to just create a memberwise clone. However, there are countless other examples. For instance, if I had a mutable object representing the ARGB data of an image (perhaps due to the image being generated pixel by pixel with the use of some mathematical formulas), and then wanted to pass it to a method which should not be able to modify it, the problem would still appear.

Create a ReadOnlySizeInfo interface containing the getters, make SizeInfo implement that interface, and pass the interface instead of SizeInfo to onSizeChanged() .

You will still pass a mutable object, but the Figure doesn't know about it: all it knows about what it receives is that it's a ReadOnlySizeInfo . It could still cast and mutate the object, but that wouldn't be a bug anymore: it would be an evil behavior.

You could create an interface, say SizeInfoView , which contains only the getters. Then SizeInfo would implement that interface, but add setters as well. Figure would only receive a reference to the SizeInfoView interface. The caller can of course still downcast to a SizeInfo , but you'd have the same issue in C++ with const_cast . It's usually good enough to prevent accidents.

Bear in mind, though, that you're getting an unmodifiable object, not an immutable object. The difference is that somebody else could modify it, and the changes would be reflected in the unmodifyable view. But again, the same is true for C++ const references.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM