【问题标题】:mutable and immutable classes可变和不可变类
【发布时间】:2012-04-27 14:54:23
【问题描述】:

我想在java中创建可变节点和不可变节点,除了可变节点之外,两者都应该相同。如何实现可变类和不可变类的基类和两个派生类?

【问题讨论】:

  • 这是什么“节点”?上下文是什么?这些类应该开放继承吗?

标签: java oop immutability mutable


【解决方案1】:

可变类和不可变类之间的区别在于,不可变类没有设置器或任何其他修改内部状态的方法。状态只能在构造函数中设置。

调用父类 Immutable 是个坏主意,因为当您有子类时,这将不再适用。该名称会产生误导:

ImmutableNode node = new MutableNode();
((MutableNode)node).change();

【讨论】:

  • 即使没有 setters 类也可以使用反射进行修改。只有最终变量才能保证不变性
  • @myx:我同意字段应该在不可变类中标记为 final(但原因与您建议的不同)。然而,这在这里行不通,因为那时他的子类化想法是不可能的。可变类也不能修改最终字段。虽然说实话,我不喜欢首先使用继承来实现可变性的想法。还有其他方法可以在不使用继承的情况下共享代码:例如接口和组合。
  • @myx --- bzzztt。您可以通过反射更改最终变量......除了静态最终字段符合编译时常量表达式。然而,通过反射改变私有和/或最终变量被认为是不好的坏习惯......并且只能作为绝望的最后措施。
【解决方案2】:

您需要做的就是创建一个带有受保护变量的基类

public class Base{
     protected int foo;
}

可变的需要能够设置变量

public class MutableBase extends Base{
     public void setFoo(){}
}

不可变对象只需要设置一次变量

public class ImmutableBase extends Base{
     public ImmutableBase(int foo){
          this.foo = foo;
     }
}

大多数不可变类都有方法来作用于内部的变量而不改变实例。字符串这样做,你可能想要这样的东西

public ImmutableBase add(int bar){
     return new ImmutableBase(this.foo+bar);
}

这很酷的一点是,您可以让您班级的用户更少控制/担心每个实例的内部结构。这使得使用起来更容易,因为在 Java 中,所有内容都是通过对象引用传递的,所以如果您传递的是 String 或 ImmutableBase,您不必担心它会被更改。

【讨论】:

  • 编译错误。如果你这样做,foo 不能是私人的。
  • 将它自己的字段声明为 final 怎么样?
  • 这不会使类不可变,它使变量只允许一个赋值。可变性作为一个概念可以说只适用于对象。不可变类可能有可变成员,也可能没有。原始类型没有什么可改变的——从某种意义上说,你不能将 4 重新分配为等于“foo”,它们是不可变的,但这是一个延伸
【解决方案3】:

不可变类是一个一旦创建就不能改变其内容的类。不可变对象是状态不能改变的对象。

Java 中不可变类的一个常见示例是String class。

【讨论】:

    【解决方案4】:

    对于一个不可变的类,它必须被声明为 final,并且它不能有 setter 方法。最终声明确保它不能被扩展和添加额外的可变属性。

    class Base {
        protected int var1;
        protected int var2;
    
        public getVar1() {return var1;}
        public getVar2() {return var2;}
        }
    
        class Mutable extends Base {
          public setVar1(int var1) {this.var1 = var1}
          public setVar2(int var2) {this.var2 = var2}
        }
    
        final class Immutable extends Base { //final to avoid being extended and then implement the setters
    
        }
    

    这就是我能做的一点点吗?但是为什么需要这样的场景呢?

    【讨论】:

    • 将它自己的字段声明为 final 怎么样?
    • 因为他想要一个可变版本?如果该字段是 final 则无法设置,但可变版本需要设置该字段。
    • 虽然只有 finalFoo 可以“保证”Foo 的实例不会真正成为某些邪恶的可变派生 Foo 的实例,但有时它拥有一个指定为不可变的可继承甚至抽象的类可能会有所帮助(这意味着任何可变的派生类都将被视为“损坏”)。例如,在某些情况下,定义一个ImmutableMatrix 抽象类可能很有用,其派生类包括ImmutableArrayMatrix(由与矩阵大小相同的数组支持)、ImmutableConstantMatrix(由...
    • ...定义宽度和高度的整数,以及一个定义所有元素值的元素),ImmutableMergedMatrix(它将包含对两个或多个 ImmutableMatrix 实例的引用,并且,对于每个,一些整数定义应该应用于最终矩阵的该矩阵的部分)等。如果特定应用程序将使用其内容符合某些与任何预先存在的实现不匹配的模式的矩阵,它可能是有用的能够定义一个新类。可以肯定的是,违反不变性的派生类会破坏事物,但是错误...
    • ...将与未能遵守不变性规范的类的覆盖版本有关。
    【解决方案5】:

    另一种选择是使用与UnmodifiableList 相同的策略。首先,创建一个指定类型的接口。

    interface List<T>{
        List<T> add(T t);
        T getAt(int i);
        ...
    }
    

    然后你用所有的业务逻辑实现你的可变类:

    public class MutableList<T> implements List<T>{
        @Override
        List<T> add(T t){ ... }
    
        @Override
        T getAt(int i){ ... }
    
        ...
    }
    

    最后,创建不可变类作为可变类的视图。您实现了相同的接口,但将所有读取方法调用委托给查看的对象,并通过异常禁止任何写入访问。

    public class UnmodifiableList<T> implements List<T>{
        //This guy will do all hard work
        private List delegate;
    
        public UnmodifiableList(List<? extends T> delegate){
            this.delegate = delegate;
        }
    
        //Forbidden mutable operation: throw exception!
        @Override
        List<T> add(T t){ 
            throw new UnsupportedOperationException("List is unmodifiable!");
        }
    
        //Allowed read operation: delegate
        @Override
        T getAt(int i){ 
            return delegate.getAt(i);
        }
    
        ...
    }
    

    这种方法的好处是您只需实现一次业务逻辑,并且可以先使用自己的方法和验证检查构建一个对象,然后再将其转换为不可变对象。

    【讨论】:

    • 重要的是要注意提供潜在可变对象的只读视图的对象与提供他们自己创建的对象的只读视图的对象之间的区别,并且从不在任何上下文中公开可能会变异它。在这方面,“不可修改”一词有点含糊。我更喜欢“ReadableFoo”之类的术语来指代已知可读且不能直接修改但可以通过其他方式更改的事物,而“ImmutableFoo”则指代保证永远不会更改的事物改变,时期。 “不可修改”似乎是一个奇怪的中间立场。
    • 是的,你是对的。原始列表的持有者可以更改其内容,这将暴露给视图。另一方面,视图在计算和内存方面效率更高。正确的工具取决于用途,不幸的是,提问者没有提供太多关于它的信息......
    猜你喜欢
    • 1970-01-01
    • 2010-11-20
    • 2011-12-24
    • 1970-01-01
    • 2013-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多