【问题标题】:What does this mean about returning a java immutable result?这对返回 java 不可变结果意味着什么?
【发布时间】:2012-10-08 09:22:57
【问题描述】:

“为原始数据返回一个不可变的接口。然后您可以更改对象中的字段,但调用者不能,除非他通过强制转换作弊。您只公开您希望用户拥有的方法。对类做同样的事情是更棘手,因为子类必须公开其超类所做的一切”

他的意思是你可以作弊,为什么子类很棘手?

来源:http://mindprod.com/jgloss/immutable.html

【问题讨论】:

    标签: java


    【解决方案1】:

    您提供了一个没有变异方法的interface。然后,您提供只有创建者知道的可变实现。

    public interface Person
    {
        String getName();
    }
    
    public class MutablePerson implements Person
    {
        private String name;
    
        public MutablePerson(String name)
        {
            this.name = name;
        }
    
        @Override
        public String getName()
        {
            return name;
        }
    
        public void setName(String name)
        {
            this.name = name;
        }
    }
    

    此时,如果您到处返回Person 对象,那么有人修改返回的对象的唯一方法就是作弊并将其转换回MutablePerson。实际上,可变对象变为不可变对象,除非代码完全被破解。

    Person person = new MutablePerson("picky");
    // someone is cheating:
    MutablePerson mutableAgain = (MutablePerson)person;
    mutableAgain.setName("Phoenix");
    
    // person.getName().equals("Phoenix") == true
    

    如果不与一群注意到真正实现是可变的年轻程序员打交道,因此他们可以强制转换它来更改它,那么您可以提供不变性的安全性,并且能够在没有无限的构造函数,或者使用 Builder(实际上,可变版本是 Builder)。避免开发人员滥用可变版本的一个好方法是将可变版本保留为包私有,以便只有包知道它。这个想法的负面影响是,这只适用于它在同一个包中实例化的情况,这可能是这种情况,但显然在诸如 DAO 与多个包定义的实现一起使用的情况下可能不是这种情况(例如、MySQL、Oracle、Hibernate、Cassandra 等,都返回相同的东西,并希望彼此分开以避免混乱它们的包)。

    这里真正的关键是人们不应该从 Mutable 对象构建,除非实现更底层的接口。如果您正在扩展,然后返回一个不可变的子类,那么根据定义,如果它公开了一个可变对象,那么它就不是不可变的。例如:

    public interface MyType<T>
    {
        T getSomething();
    }
    
    public class MyTypeImpl<T> implements MyType<T>
    {
        private T something;
    
        public MyTypeImpl(T something)
        {
            this.something = something;
        }
    
        @Override
        public T getSomething()
        {
            return something;
        }
    
        public void setSomething(T something)
        {
            this.something = something;
        }
    }
    
    public interface MyExtendedType<T> extends MyType<T>
    {
        T getMore();
    }
    
    public class MyExtendedTypeImpl<T>
            extends MyTypeImpl<T>
            implements MyExtendedType<T>
    {
        private T more;
    
        public MyExtendedTypeImpl(T something, T more)
        {
            super(something);
    
            this.more = more;
        }
    
        @Override
        public T getMore()
        {
            return more;
        }
    
        public void setMore(T more)
        {
            this.more = more;
        }
    }
    

    老实说,Java 中的Collections 应该是这样实现的。只读接口可以取代Collections.unmodifiable 实现,因此不会让人意外地使用可变对象的不可变版本。换句话说,你永远不应该隐藏不变性,但你可以隐藏可变性。

    然后,他们可以散布真正无法修改的不可变实例,这将使开发人员保持诚实。同样,我可能希望在某处看到上述接口的不可变版本(具有更好的名称):

    public class MyTypeImmutable<T> implements MyType<T>
    {
        private final T something;
    
        public MyTypeImmutable(T something)
        {
            this.something = something;
        }
    
        @Override
        public T getSomething()
        {
            return something;
        }
    }
    

    【讨论】:

    • 真的很完整,有很好的例子! +1
    【解决方案2】:

    我认为该声明措辞不当,他所触及的不仅仅是不变性(事实上,该声明甚至不是真正的不变性)。

    这个想法是,如果您返回数据的接口,而不是特定的类,调用者应该只执行接口上的操作。所以如果你的接口只有 getter 方法,那么应该没有办法操作数据(没有向下转换)。

    考虑这种层次结构

    interface AnInterface {
      void aGetter();
    }
    
    class MyMutableClass {
       void aGetter();
       void aSetter(...);
    }
    

    即使MyMutableClass 是可变的,通过返回AnInterface,用户并不知道它实际上是一个可变对象。所以该对象实际上并不是可变的,但您必须向下转换(或使用反射来访问 mutator 方法)才能知道这一点。

    现在假设你有

    class MyImmutableSubclass extends MyMutableClass {
       void anotherGetter();
    }
    

    即使子类是“不可变的”(它不是真的,因为它的父类不是不可变的),如果你从方法返回MyImmutableSubclass,调用者仍然可以调用aSetter,因为MyMutableClass 暴露了它。

    一般来说,建议使用不可变对象以避免“泄漏”状态。任何真正不可变的东西都不会受到任何操纵和意外更改。

    【讨论】:

    • 你的最后一句话暗示了一些,但不变性也保证了线程安全,因为在执行过程中不存在引用更改的风险。通过隐藏可变性实现的不变性在线程执行期间被视为不可变的特定情况下提供了相同的好处。换句话说,仅仅因为一个线程认为它是不可变的,创建者就不能在幕后修改它。这种类型的可变性实际上是一种方便的方式,可以将一个对象放在一起,一旦它表示为不可变,就应该“忘记”它,从而返回。
    【解决方案3】:

    您可以作弊,因为如果您将类型转换为可变的“某物”,您可以更改返回字段的类型。如果您将您的类“隐藏”在公共接口后面并返回该不可变接口,则用户可以通过将您的接口类型转换为您的类来作弊。

    使用子类比较棘手,因为类的任何私有成员都不会被子类继承,但受保护的和公共的却是。这意味着您可以从外部在父类中访问的任何内容也可以从外部在子类中访问,因此您不能像使用界面那样容易地混淆用户。我认为这实际上是可能的,因为您可以覆盖父方法,尽管我认为这没什么意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-03
      • 1970-01-01
      • 2010-09-18
      • 2021-04-30
      • 1970-01-01
      • 1970-01-01
      • 2023-01-03
      • 1970-01-01
      相关资源
      最近更新 更多