Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I throw exception on mutation attempt of immutable class? If so, which exception?

I wish to alert the developer when he attempts to mutate an immutable object. The immutable object is actually an extension of a mutable object, and overrides the setters on said object to make it immutable.

Mutable base class: Vector3

public class Vector3 {
    public static final Vector3 Zero = new ImmutableVector3(0, 0, 0);

    private float x;
    private float y;
    private float z;

    public Vector3(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public void set(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Immutable version: ImmutableVector3

public final class ImmutableVector3 extends Vector3 {
    public ImmutableVector3(float x, float y, float z) {
        super(x, y, z);
    }

    //Made immutable by overriding any set functionality.
    @Override
    public void set(float x, float y, float z) {
        throw new Exception("Immutable object."); //Should I even throw an exception?
    }
}

My use case is as follows:

public class MyObject {
    //position is set to a flyweight instance of a zero filled vector.
    //Since this is shared and static, I don't want it mutable.
    private Vector3 position = Vector3.Zero;
}

Let's say that a developer on my team decides he needs to manipulate the position of the object, but currently it's set to the static, immutable Vector3.Zero shared vector instance.

It would be best if he knew ahead of time that he needs to create a new instance of a mutable vector if the position vector is set to the shared instance of Vector3.Zero:

if (position == Vector3.Zero)
    position = new Vector3(x, y, z);

But, let's say he doesn't check this first. I think, in this case, it would be good to throw an Exception as shown above in the ImmutableVector3.set(x,y,z) method. I wanted to use a standard Java exception, but I wasn't sure which would be most fitting, and I'm not 100% convinced this is the best way to handle this.

The suggestions I've seen on (this question)[Is IllegalStateException appropriate for an immutable object? seem to suggest the following:

  • IllegalStateException - but it's an immutable object, thus only has a single state
  • UnsupportedOperationException - the set() method is unsupported since it overrides the base class, so this might be a good choice.
  • NoSuchElementException - I'm not sure how this would be a good choice unless the method is considered an element.

Furthermore, I'm not even sure I should throw an exception here. I could simply not change the object, and not alert the developer that they are trying to change an immutable object, but that seems troublesome as well. The problem with throwing an exception in ImmutableVector3.set(x,y,z) is that set has to throw an Exception on both ImmutableVector3 and Vector3. So now, even though Vector3.set(x,y,z) will never throw an exception, it has to be placed in a try/catch block.

Questions
  • Am I overlooking another option besides throwing exceptions?
  • If exceptions are the best way to go, which exception should I choose?
like image 402
crush Avatar asked Aug 29 '13 17:08

crush


People also ask

Can immutable classes be subclassed?

If you want to enforce immutability, you cannot have subclasses. See for example java. lang. String, which is a final class for this reason: To prevent people from subclassing String to make it mutable.

How can we prevent immutable class?

How to preserve Immutability? There are two ways to avoid this problem, first, don't provide getters to mutable objects if you can avoid it. If you must, then consider returning a copy or clone of the mutable object. If you are returning a collection, you could wrap it as an unmodifiable collection.

What is a disadvantage of immutable classes?

The only real disadvantage of immutable classes is that they require a separate object for each distinct value. Creating these objects can be costly, especially if they are large.


4 Answers

Throwing an exception is usually the way to go, and you should use UnsupportedOperationException.

The Java API itself does recommend this in the collections framework:

The "destructive" methods contained in this interface, that is, the methods that modify the collection on which they operate, are specified to throw UnsupportedOperationException if this collection does not support the operation.

An alternative if you create the full class hierarchy is to create a super class Vector that is not modifiable and has no modifying methods like set(), and two sub-classes ImmutableVector (that guarantees immutability) and MutableVector (that is modifiable). This would be better because you get compile-time safety (you can't even try to modify an ImmutableVector), but depending on the situation might be over-engineered (and you might end up with many classes).

This approach was for example chosen for the Scala collections, they all exist in three variations.

In all cases where you want to modify the vector, you use the type MutableVector. In all cases where you don't care about modifying and just want to read from it, you just use Vector. In all cases where you want to guarantee immutability (e.g., a Vector instance is passed into a constructor and you want to ensure that nobody will modify it later) you use ImmutableVector (which would usually provide a copy method, so you just call ImmutableVector.copyOf(vector)). Down-casting from Vector should never be necessary, specify the correct sub-type that you need as type instead. The whole point of this solution (which needs more code) is about being type safe and with compile-time checks, so down-casting destroy that benefit. There are exceptions, though, for example the ImmutableVector.copyOf() method would, as an optimization, usually look like this:

public static ImmutableVector copyOf(Vector v) {
  if (v instanceof ImmutableVector) {
    return (ImmutableVector)v;
  } else {
    return new ImmutableVector(v);
  }
}

This is still safe and gives you one further benefit of immutable types, namely the avoidance of unnecessary copying.

The Guava library has a large collection of immutable collection implementations that follow these pattern (although they still extend the mutable standard Java collection interfaces and thus provide no compile-time safety). You should read the description of them to learn more about immutable types.

The third alternative would be to just have on immutable vector class, and no mutable classes at all. Very often mutable classes used for performance reasons are a premature optimization, as Java is very good in handling lots of small short-lived temporary objects (due to a generational garbage collector, object allocation is extremely cheap, and object cleanup can even be without any cost in the best case). Of course this is no solution for very large objects like lists, but for a 3-dimensional vector this is surely an option (and probably the best combination because its easy, safe, and needs less code than the others). I do not know any examples in the Java API of this alternative, because back in the days when most parts of the API were created, the VM was not that optimized and so too many objects were maybe a performance problem. But that should not prevent you from doing so today.

like image 112
Philipp Wendler Avatar answered Oct 28 '22 19:10

Philipp Wendler


You should throw a compiler error if you try to mutate an immutable class.

Immutable classes should not extend mutable ones.

like image 23
SLaks Avatar answered Oct 28 '22 20:10

SLaks


Given your situation, I think you should throw an UnsupportedOperationException. Here's why:

package com.sandbox;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Sandbox {

    public static void main(String[] args) throws IOException {
        List<Object> objects = Collections.unmodifiableList(new ArrayList<Object>());
        objects.add(new Object());
    }

}

This will throw a UnsupportedOperationException and is a very similar situation to yours.

But an even better thing to do (which may not be possible) is to design your immutable classes so there isn't a mutating method to call in the first place. As a developer, I don't want to learn at runtime that I can't use a method, I want to learn at or before compile time.

Your set method should return a new ImmutableVector which doesn't mutate anything.

You might want to take @SotiriosDelimanolis's idea, and make both classes implement an interface without a setter and then have your code pass around the interface. Again, this may not be possible given your legacy code.

like image 45
Daniel Kaplan Avatar answered Oct 28 '22 19:10

Daniel Kaplan


Immutable classes should not derive from concrete mutable classes, nor vice versa. Instead, you should have a ReadableFoo abstract class with concrete MutableFoo and ImmutableFoo derivatives. That way a method which wants an object it can mutate can demand MutableFoo, a method which wants an object whose present state can be persisted merely by persisting a reference to the object can demand ImmutableFoo, and a method which won't want to change an object state and won't care what the state does after it returns can freely accept ReadableFoo without having to worry about whether they're mutable or immutable.

The trickiest aspect is deciding whether it's better to have the base class include a method to check whether it's mutable along with a "set" method which will throw if it isn't, or have it simply omit the "set" method. I would favor omitting the "set" method, unless the type includes features to facilitate a copy-on-write pattern.

Such a pattern may be facilitated by having the base class include abstract methods AsImmutable, AsMutable, along with concrete AsNewMutable. A method which is given a ReadableFoo named George and wants to capture its present state can store to a field GeorgeField either George.AsImmutable() or George.AsNewMutable() depending upon whether it thinks it will want to modify it. If it does need to modify it, it can set GeorgeField = GeorgeField.AsMutable(). If it needs to share it, it can return GeorgeField.AsImmutable(); if it expects to share it many times before changing it again, it can set GeorgeField = GeorgeField.AsImmutable(). If many objects need to share the states ...Foos, and most of them don't need to modify them at all but some need to modify them a lot, such an approach may work out more efficiently than using mutable or immutable objects alone.

like image 26
supercat Avatar answered Oct 28 '22 21:10

supercat