Share via


IFreezable Interface

Definition

Provides a flexible mechanism for controlling access, without requiring that a class be immutable.

[Android.Runtime.Register("android/icu/util/Freezable", "", "Android.Icu.Util.IFreezableInvoker", ApiSince=24)]
[Java.Interop.JavaTypeParameters(new System.String[] { "T" })]
public interface IFreezable : IDisposable, Java.Interop.IJavaPeerable, Java.Lang.ICloneable
[<Android.Runtime.Register("android/icu/util/Freezable", "", "Android.Icu.Util.IFreezableInvoker", ApiSince=24)>]
[<Java.Interop.JavaTypeParameters(new System.String[] { "T" })>]
type IFreezable = interface
    interface ICloneable
    interface IJavaObject
    interface IDisposable
    interface IJavaPeerable
Derived
Attributes
Implements

Remarks

Provides a flexible mechanism for controlling access, without requiring that a class be immutable. Once frozen, an object can never be unfrozen, so it is thread-safe from that point onward. Once the object has been frozen, it must guarantee that no changes can be made to it. Any attempt to alter it must raise an UnsupportedOperationException exception. This means that when the object returns internal objects, or if anyone has references to those internal objects, that those internal objects must either be immutable, or must also raise exceptions if any attempt to modify them is made. Of course, the object can return clones of internal objects, since those are safe. <h2>Background</h2>

There are often times when you need objects to be objects 'safe', so that they can't be modified. Examples are when objects need to be thread-safe, or in writing robust code, or in caches. If you are only creating your own objects, you can guarantee this, of course -- but only if you don't make a mistake. If you have objects handed into you, or are creating objects using others handed into you, it is a different story. It all comes down to whether you want to take the Blanche Dubois approach (&quot;depend on the kindness of strangers&quot;) or the Andy Grove approach (&quot;Only the Paranoid Survive&quot;).

For example, suppose we have a simple class:

public class A {
                 protected Collection b;

                 protected Collection c;

                 public Collection get_b() {
                         return b;
                 }

                 public Collection get_c() {
                         return c;
                 }

                 public A(Collection new_b, Collection new_c) {
                         b = new_b;
                         c = new_c;
                 }
            }

Since the class doesn't have any setters, someone might think that it is immutable. You know where this is leading, of course; this class is unsafe in a number of ways. The following illustrates that.

public test1(SupposedlyImmutableClass x, SafeStorage y) {
               // unsafe getter
               A a = x.getA();
               Collection col = a.get_b();
               col.add(something); // a has now been changed, and x too

               // unsafe constructor
               a = new A(col, col);
               y.store(a);
               col.add(something); // a has now been changed, and y too
             }

There are a few different techniques for having safe classes.

<ol> <li>Const objects. In C++, you can declare parameters const.</li> <li>Immutable wrappers. For example, you can put a collection in an immutable wrapper.</li> <li>Always-Immutable objects. Java uses this approach, with a few variations. Examples: <ol> <li>Simple. Once a Color is created (eg from R, G, and B integers) it is immutable.</li> <li>Builder Class. There is a separate 'builder' class. For example, modifiable Strings are created using StringBuffer (which doesn't have the full String API available). Once you want an immutable form, you create one with toString().</li> <li>Primitives. These are always safe, since they are copied on input/output from methods.</li> </ol> </li> <li>Cloning. Where you need an object to be safe, you clone it.</li> </ol>

There are advantages and disadvantages of each of these.

<ol> <li>Const provides a certain level of protection, but since const can be and is often cast away, it only protects against most inadvertent mistakes. It also offers no threading protection, since anyone who has a pointer to the (unconst) object in another thread can mess you up.</li> <li>Immutable wrappers are safer than const in that the constness can't be cast away. But other than that they have all the same problems: not safe if someone else keeps hold of the original object, or if any of the objects returned by the class are mutable.</li> <li>Always-Immutable Objects are safe, but usage can require excessive object creation.</li> <li>Cloning is only safe if the object truly has a 'safe' clone; defined as one that ensures that no change to the clone affects the original. Unfortunately, many objects don't have a 'safe' clone, and always cloning can require excessive object creation.</li> </ol> <h2>Freezable Model</h2>

The Freezable model supplements these choices by giving you the ability to build up an object by calling various methods, then when it is in a final state, you can make it immutable. Once immutable, an object cannot ever be modified, and is completely thread-safe: that is, multiple threads can have references to it without any synchronization. If someone needs a mutable version of an object, they can use cloneAsThawed(), and modify the copy. This provides a simple, effective mechanism for safe classes in circumstances where the alternatives are insufficient or clumsy. (If an object is shared before it is immutable, then it is the responsibility of each thread to mutex its usage (as with other objects).)

Here is what needs to be done to implement this interface, depending on the type of the object.

<h3><b>Immutable Objects</b></h3>

These are the easiest. You just use the interface to reflect that, by adding the following:

public class A implements Freezable&lt;A&gt; {
              ...
              public final boolean isFrozen() {return true;}
              public final A freeze() {return this;}
              public final A cloneAsThawed() { return this; }
              }

These can be final methods because subclasses of immutable objects must themselves be immutable. (Note: freeze is returning this for chaining.)

<h3><b>Mutable Objects</b></h3>

Add a protected 'flagging' field:

protected volatile boolean frozen; // WARNING: must be volatile

Add the following methods:

public final boolean isFrozen() {
                 return frozen;
            };

            public A freeze() {
                 frozen = true;  // WARNING: must be final statement before return
                 return this;
            }

Add a cloneAsThawed() method following the normal pattern for clone(), except that frozen=false in the new clone.

Then take the setters (that is, any method that can change the internal state of the object), and add the following as the first statement:

if (isFrozen()) {
                 throw new UnsupportedOperationException(&quot;Attempt to modify frozen object&quot;);
            }

<h4><b>Subclassing</b></h4>

Any subclass of a Freezable will just use its superclass's flagging field. It must override freeze() and cloneAsThawed() to call the superclass, but normally does not override isFrozen(). It must then just pay attention to its own getters, setters and fields.

<h4><b>Internal Caches</b></h4>

Internal caches are cases where the object is logically unmodified, but internal state of the object changes. For example, there are const C++ functions that cast away the const on the &quot;this&quot; pointer in order to modify an object cache. These cases are handled by mutexing the internal cache to ensure thread-safety. For example, suppose that UnicodeSet had an internal marker to the last code point accessed. In this case, the field is not externally visible, so the only thing you need to do is to synchronize the field for thread safety.

<h4>Unsafe Internal Access</h4>

Internal fields are called safe if they are either frozen or immutable (such as String or primitives). If you've never allowed internal access to these, then you are all done. For example, converting UnicodeSet to be Freezable is just accomplished with the above steps. But remember that you <b>have</b> allowed access to unsafe internals if you have any code like the following, in a getter, setter, or constructor:

Collection getStuff() {
                 return stuff;
            } // caller could keep reference &amp; modify

            void setStuff(Collection x) {
                 stuff = x;
            } // caller could keep reference &amp; modify

            MyClass(Collection x) {
                 stuff = x;
            } // caller could keep reference &amp; modify

These also illustrated in the code sample in <b>Background</b> above.

To deal with unsafe internals, the simplest course of action is to do the work in the freeze() function. Just make all of your internal fields frozen, and set the frozen flag. Any subsequent getter/setter will work properly. Here is an example:

<b>Warning!</b> The 'frozen' boolean MUST be volatile, and must be set as the last statement in the method.

public A freeze() {
                 if (!frozen) {
                         foo.freeze();
                         frozen = true;
                 }
                 return this;
            }

If the field is a Collection or Map, then to make it frozen you have two choices. If you have never allowed access to the collection from outside your object, then just wrap it to prevent future modification.

zone_to_country = Collections.unmodifiableMap(zone_to_country);

If you have ever allowed access, then do a clone() before wrapping it.

zone_to_country = Collections.unmodifiableMap(zone_to_country.clone());

If a collection (or any other container of objects) itself can contain mutable objects, then for a safe clone you need to recurse through it to make the entire collection immutable. The recursing code should pick the most specific collection available, to avoid the necessity of later downcasing.

<blockquote>

<b>Note: </b>An annoying flaw in Java is that the generic collections, like Map or Set, don't have a clone() operation. When you don't know the type of the collection, the simplest course is to just create a new collection:

zone_to_country = Collections.unmodifiableMap(new HashMap(zone_to_country));

</blockquote>

Java documentation for android.icu.util.Freezable.

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

Properties

Handle

Gets the JNI value of the underlying Android object.

(Inherited from IJavaObject)
IsFrozen
JniIdentityHashCode

Returns the value of java.lang.System.identityHashCode() for the wrapped instance.

(Inherited from IJavaPeerable)
JniManagedPeerState

State of the managed peer.

(Inherited from IJavaPeerable)
JniPeerMembers

Member access and invocation support.

(Inherited from IJavaPeerable)
PeerReference

Returns a JniObjectReference of the wrapped Java object instance.

(Inherited from IJavaPeerable)

Methods

CloneAsThawed()

Provides for the clone operation.

Disposed()

Called when the instance has been disposed.

(Inherited from IJavaPeerable)
DisposeUnlessReferenced()

If there are no outstanding references to this instance, then calls Dispose(); otherwise, does nothing.

(Inherited from IJavaPeerable)
Finalized()

Called when the instance has been finalized.

(Inherited from IJavaPeerable)
Freeze()

Freezes the object.

SetJniIdentityHashCode(Int32)

Set the value returned by JniIdentityHashCode.

(Inherited from IJavaPeerable)
SetJniManagedPeerState(JniManagedPeerStates) (Inherited from IJavaPeerable)
SetPeerReference(JniObjectReference)

Set the value returned by PeerReference.

(Inherited from IJavaPeerable)
UnregisterFromRuntime()

Unregister this instance so that the runtime will not return it from future Java.Interop.JniRuntime+JniValueManager.PeekValue invocations.

(Inherited from IJavaPeerable)

Extension Methods

JavaCast<TResult>(IJavaObject)

Performs an Android runtime-checked type conversion.

JavaCast<TResult>(IJavaObject)
GetJniTypeName(IJavaPeerable)

Gets the JNI name of the type of the instance self.

JavaAs<TResult>(IJavaPeerable)

Try to coerce self to type TResult, checking that the coercion is valid on the Java side.

TryJavaCast<TResult>(IJavaPeerable, TResult)

Try to coerce self to type TResult, checking that the coercion is valid on the Java side.

Applies to