du369
du369

Reputation: 821

How to safely publish a mutable object?

I don't want to do the deep copy way.

Say, I have a field of some mutable type, a x,y,z Coordinate for example. Occasionally, I need to expose this field to some viewers. And I want it be read-only. I remember reading something like a wrapper to do these kind of stuff, but I don't remember the details.

The x,y,z Coordinate example may be too simple because x,y,z are primitive type. So getX() always return a copy.

I want a general solution even if the x,y,z fields are of yet another mutable type.

Can anybody help?


EDIT:

public class Client
{

    public static final Holder holder = new Holder();


    public static void main(String[] args)
    {

        UserWrapper user = holder.getUser();
        System.out.println(user);               //UserWrapper{user=User{address=Address{street='street 101'}}}
        user.getAddress().setStreet("mars");    //UserWrapper{user=User{address=Address{street='mars'}}}
        System.out.println(user);

    }
}

public class Holder
{

    private User user;

    public Holder()
    {
        user = new User();
        Address address = new Address();
        address.setStreet("street 101");
        user.setAddress(address);
    }

    public UserWrapper getUser()
    {
        return new UserWrapper(user);
    }

}

public class User
{

    private Address address;

    public Address getAddress()
    {
        return address;
    }

    public void setAddress(Address address)
    {
        this.address = address;
    }
}

public class UserWrapper
{

    private User user;

    public UserWrapper(User user)
    {
        this.user = user;
    }

    public Address getAddress()
    {
        return user.getAddress();
    }
}

EDIT: credit to I don't know who(he deletes the answer), I find this link he mentioned in his original post very helpful.

Upvotes: 1

Views: 155

Answers (3)

ethanfar
ethanfar

Reputation: 3778

There is no built-in mechanism in Java that will enable you to do that. Usually, if you move instances around, you'd either:

  1. Use immutable objects
  2. Pass on copies of the objects

Since you don't want/can't choose either of these ways, you'll need to use an alternative. There are a lot of different ways to implement this depending on your requirements and how complex is your class structure, but the general approach would be to publish an immutable wrapper instead of the original.
Here are some examples:

public class XYZ {
    public int x, y, z;
}

public class XYZWrapper {
    private XYZ xyz;

    public XYZWrapper(XYZ xyz) {
        this.xyz = xyz;
    }

    public int getX() { return x; }
    public int getY() { return y; }
    public int getZ() { return z; }
}

public class Address {
    public String name;
    public XYZ xyz;
}

public class AddressWrapper {
    private String name; // Note that this could be public since any String is immutable
    private XYZWrapper xyzWrapper;

    public AddressWrapper(String name, XYZ xyz) {
        this.name = name;
        this.xyzWrapper = new XYZWrapper(xyz);
    }

    public String getName() { 
        return name;
    }

    public XYZWrapper getXYZWrapper() { 
        return xyzWrapper;
    }
}

Now, if instead of XYZ and Address classes, you work with interfaces, you can have 2 implementations (e.g. XYZMutable & XYZImmutable) which will allow you to abstract which type of class you're returning, and also will enable you to create an instance of XYZImmutable from an instance of XYZMutable (assuming that the interface defines only & all getter methods).

One more note about this approach (especially if you do it the preferred way by using interfaces): Even if you have a complex class hierarchy, you can do this relatively effortlessly by creating a generator class that receives an interface instance, a mutable implementation instance and returns an immutable implementation instance as the return value.

Upvotes: 2

Klitos Kyriacou
Klitos Kyriacou

Reputation: 11642

Perhaps you're thinking of the "copy on write" idiom. This allows you to avoid copying unless you have to. It's use is generally not recommended because it is not thread-safe unless you use synchronization which will unnecessarily slow down single-threaded applications.

It works by keeping a reference count of its internal data; something like this untested bit of code:

public class User
{
    private int addressReferenceCount;
    private Address address;

    public User(Address address) {
        addressReferenceCount = 0;
        this.address = address;
    }

    public Address getAddress() {
        addressReferenceCount++;
        return address;
    }

    public void setAddress(Address address)
    {
        if (addressReferenceCount == 0) {
            this.address = address;
        }
        else {
            this.address = new Address(address);
            addressReferenceCount = 0;
        }
    }
}

This ensures that user code like this will get different addresses when necessary:

User u = new User(new Address("1 Acacia Avenue"));
Address oldAddress = u.getAddress();
Address stillOldAddress = u.getAddress();
u.setAddress(new Address("2 Acacia Avenue"));
Address newAddress = u.getAddress();
assert (oldAddress == stillOldAddress); // both refer to same object
assert (oldAddress != newAddress);

Upvotes: 1

Edwin Buck
Edwin Buck

Reputation: 70949

The traditional ways:

  • deep copy - prevents mutations from impacting the client who is reading
  • immutable objects - instead of copying for the client, you copy to update and the client gets an old pointer reference.
  • customer iterator - you provide your own iterator / navigation interface, which is sensitive to a "version" field embedded with the data structure. Before visiting each element, it checks that the version has not been changed since the iterator was created (java collections does this).
  • strong synchronization - while a reader is reading, the reader holds a lock on the data structure preventing update. Generally a bad solution, but occasionally useful (included for completeness).
  • lazy copy - you construct an object that mostly references the original, but is triggered (as a listener) to the original, such that when a mutation is done on the original, you copy the pre-mutated value locally. This is like a lazy deep copy strategy.

There's others, but this should get you started.

Upvotes: 2

Related Questions