我应该对不可变类的变异尝试抛出异常吗?如果是这样,哪个例外?

cru*_*ush 7 java exception immutability

我希望在他试图改变不可变对象时提醒开发人员.不可变对象实际上是可变对象的扩展,并覆盖所述对象上的setter以使其不可变.

可变基类: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;
    }
}
Run Code Online (Sandbox Code Playgroud)

不可变版本: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?
    }
}
Run Code Online (Sandbox Code Playgroud)

我的用例如下:

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;
}
Run Code Online (Sandbox Code Playgroud)

假设我的团队中的开发人员决定他需要操纵对象的位置,但是目前它被设置为静态的,不可变的Vector3.Zero共享向量实例.

如果他提前知道他需要创建一个可变向量的新实例,如果位置向量设置为Vector3.Zero的共享实例,那将是最好的:

if (position == Vector3.Zero)
    position = new Vector3(x, y, z);
Run Code Online (Sandbox Code Playgroud)

但是,让我们说他不会先检查这个.我认为,在这种情况下,抛出如上所示的ImmutableVector3.set(x,y,z)方法中的异常会很好.我想使用标准的Java异常,但我不确定哪个最合适,我不是100%确信这是处理这个问题的最佳方法.

我见过的建议(这个问题)[IllegalStateException是否适用于不可变对象?似乎建议如下:

  • IllegalStateException - 但它是一个不可变对象,因此只有一个状态
  • UnsupportedOperationException - 该set()方法不受支持,因为它覆盖了基类,因此这可能是一个不错的选择.
  • NoSuchElementException - 我不确定这将是一个不错的选择,除非该方法被认为是一个element.

此外,我甚至不确定我应该在这里抛出异常.我可以简单地不更改对象,也不会提醒开发人员他们正在尝试更改不可变对象,但这似乎也很麻烦.在抛出异常的问题ImmutableVector3.set(x,y,z)是,set有扔在两者的异常ImmutableVector3Vector3.所以现在,即使Vector3.set(x,y,z)永远不会抛出异常,它也必须放在一个try/catch块中.

问题
  • 除了抛出例外,我还能忽略另一种选择吗
  • 如果异常是最好的方法,我应该选择哪个例外?

Phi*_*ler 10

抛出异常通常是要走的路,你应该使用UnsupportedOperationException.

Java API本身确实在集合框架中推荐这个:

如果此集合不支持该操作,则指定此接口中包含的"破坏性"方法(即修改其操作集合的方法)将抛出UnsupportedOperationException.

如果创建完整的类层次结构,另一种方法是创建一个Vector不可修改的超类,并且没有修改方法,如set()两个子类ImmutableVector(保证不变性)和MutableVector(可修改).这样会更好,因为你获得了编译时的安全性(你甚至无法尝试修改ImmutableVector),但是根据情况可能会过度设计(最终可能会有很多类).

例如,这种方法是为Scala集合选择的,它们都存在三种变体.

在要修改矢量的所有情况下,都使用该类型MutableVector.在您不关心修改并且只想阅读它的所有情况下,您只需使用Vector.在你想要保证不变性的所有情况下(例如,一个Vector实例被传递给构造函数,并且你想确保以后没有人会修改它)你使用ImmutableVector(通常会提供一个复制方法,所以你只需要调用ImmutableVector.copyOf(vector)).从Vector不需要向下转换,请指定您需要的正确子类型.这个解决方案的全部要点(需要更多代码)是关于类型安全和编译时检查,所以向下销售会破坏这种好处.但也有例外,例如ImmutableVector.copyOf(),作为优化,该方法通常如下所示:

public static ImmutableVector copyOf(Vector v) {
  if (v instanceof ImmutableVector) {
    return (ImmutableVector)v;
  } else {
    return new ImmutableVector(v);
  }
}
Run Code Online (Sandbox Code Playgroud)

这仍然是安全的,并为您提供了不可变类型的另一个好处,即避免不必要的复制.

番石榴库收集了大量的不可变集合遵循这些模式(当然,它们还扩展了可变标准的Java集合接口,从而提供任何编译时安全性)的实现.您应该阅读它们的描述以了解有关不可变类型的更多信息.

第三种选择是只使用不可变的vector类,而根本没有可变类.出于性能原因而使用的可变类通常是过早优化,因为Java非常善于处理大量短暂的临时对象(由于分代垃圾收集器,对象分配非常便宜,并且对象清理甚至可以没有任何成本最好的情况下).当然,对于像列表这样的非常大的对象来说,这不是解决方案,但对于三维向量,这肯定是一种选择(可能是最好的组合,因为它简单,安全,并且需要的代码少于其他代码).我不知道此替代方案的Java API中的任何示例,因为在创建API的大多数部分的时代,VM并未进行优化,因此太多对象可能是性能问题.但这不应该阻止你今天这样做.