这对于返回java不可变结果意味着什么?

时间:2012-10-18 01:29:11

标签: java

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

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

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

3 个答案:

答案 0 :(得分:8)

您提供的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中Collection的实现方式。 readonly接口可以代替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,因为{{暴露它。

通常,建议使用不可变对象以避免“泄漏”状态。任何真正不可改变的东西都是安全的,不受任何操纵和意外的改变。

答案 2 :(得分:1)

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

使用子类比较棘手,因为类的任何私有成员都不是由子类继承的,而是受保护的和公共的。这意味着您可以从外部访问您父类中的任何内容,也可以从外部访问子类,因此您无法像使用界面那样轻松地对用户进行模糊处理。我认为这实际上是可行的,因为你可以覆盖父方法,虽然我没有看到它的重点。