How to implement immutable and read-only versions of mutable objects effectively?

Viewed 133

Context :

  • package data, public :
public interface _Data {
   public String getData();
}
public class _PackageAPI {
    DataHolder holder;

    public void createHolder(String data) {
        holder = new DataHolder();
        holder.setData(data);
    }

    public void mutateHolder(String data) {
        holder.setData(data);
    }

    public _Data getSnapshot() {
        return DataSnapshot.from(holder.getData());
    }

    public _Data getReader() {
        return holder.readOnly();
    }
}
  • package data, package-private :
class DataHolder {
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public _Data readOnly() {
        return new _Data() {
            @Override
            public String getData() {
                return DataHolder.this.data;
            }
        };
    }
}
class DataSnapshot {
    public static _Data from(String data){
        return new _Data() {
            @Override
            public String getData() {
                return data;
            }
        };
    }
}
  • sample client usage :
package clientPackage;

import data._Data;
import data._PackageAPI;

public class ExampleRunner {

    public static void main(String[] args) {

        _PackageAPI handler;

        System.out.println("Creating...");

        handler = new _PackageAPI();
        handler.createHolder("INITIAL DATA");

        System.out.println("Done creating...");

        _Data initialSnapShot =  handler.getSnapshot();
        _Data initialReader = handler.getReader();

        System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
        System.out.println("initialSnapShot class :" + initialSnapShot.getClass() );
        System.out.println("initialReader class :" + initialReader.getClass() );

        System.out.println("initialReader holds :" + initialReader.getData() );


        System.out.println("Mutating...");

        handler.mutateHolder("MUTATED DATA");
        _Data subsequentSnapShot =  handler.getSnapshot();
        _Data subsequentReader = handler.getReader();

        System.out.println("Done mutating...");


        System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
        System.out.println("initialReader holds :" + initialReader.getData() );

        System.out.println("subsequentSnapShot holds :" + subsequentSnapShot.getData() );
        System.out.println("subsequentReader holds :" + subsequentReader.getData() );



    }
}
  • And console output:
Creating...
Done creating...
initialSnapShot holds :INITIAL DATA
initialSnapShot class :class data.DataSnapshot$1
initialReader class :class data.DataHolder$1
initialReader holds :INITIAL DATA
Mutating...
Done mutating...
initialSnapShot holds :INITIAL DATA
initialReader holds :MUTATED DATA
subsequentSnapShot holds :MUTATED DATA
subsequentReader holds :MUTATED DATA
  • FIRST QUESTION : given getSnapshot() returns a _Data (of class : DataSnapshot$1) whose method getData() returns the "real" data reference, ie the content of the variable data of the DataHolder object, is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

  • FIRST QUESTION SHORTENED : is it anyhow possible to mutate content of the memory referenced by a reference, using only the reference ?

(Of course solution to this is to clone the String being referenced.)

  • SECOND QUESTION : is there anyway to mutate a DataSnapshot$1 (the "immutable" version of _Data) instance ?

  • THIRD QUESTION : given DataHolder$1 (the "readOnly" version of _Data) holds internally a reference to the DataHolder instance providing it, is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

EDIT : I would have put a paranoïd tag if there was one

1 Answers

is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

Since getData returns a String, which is immutable, the caller can't change anything through the returned reference. You can't access a DataHolder through a String either. Why would String know about your DataHolder class?

is there anyway to mutate a DataSnapshot$1?

No, because it is an anonymous inner class that only has one method that returns a parameter. Parameters are passed by value, so you don't need to worry about callers changing their values on the other side. It's also a String, which means the callers won't be mutating the object either.

You might be asking this because you saw how initialReader has changed. Well, since DataHolder$1 is an inner class of the mutable DataHolder, it's not really immutable even if it doesn't have any mutator methods.

is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

There is no way to access the outer class from the inner class, so since there are no mutator methods in DataHolder$1, you can't mutate DataHolder from the outside, with only a DataHolder$1.

However, if DataHolder changes, the changes will reflect on DataHolder$1 (as shown in your sample code), which I think defeats the purpose of immutability.


Here's how I would implement immutability in this scenario.

I would make DataHolder implement _Data. DataHolder certainly can do this, can't it? It has a String getData() method after all! DataHolder would have an asReadOnly method that creates a copy of this and returns _Data. In fact, I'd rename _Data to ReadOnlyData.

Related