非最终类可以完全不可变吗?

时间:2011-11-02 06:32:47

标签: java immutability

其中没有final修饰符的类是否可以完全不可变?

例如,以下类是不可变的吗?

class Animal
{
    private String animalName;

    public Animal(String name) {
        animalName = name;
    }

    public String getName() { return animalName; }
}

6 个答案:

答案 0 :(得分:8)

类(如您的问题所示)是不可变的,因为它的内部状态都不会改变。即使您要定义Animal类的子类,也无法更改animalName;但是,虽然Animal类是不可变的,但它的子类可能是不可变的(取决于它们的实现)。

这样做的危险在于,如果有人要将子类定义为Animal类中的内部类(如下所示),那么它们可能会违反您的不变性:

class Animal {
    private String animalName;
    public Animal(String name) {
        animalName = name;
    }
    public getName() { return animalName; }

    public class Eagle extends Animal {
        public Eagle() {
            super("Eagle");
        }
        public void foo() {
            animalName = animalName + "!";
        }
    }
}

出于这个原因,尽可能使用private可见性和final修饰符非常棒。这将防止人们意外地引入违反您想要施加的不可变性或封装约束的代码。这样,必须有意识地决定程序员增加可见性或删除final关键字,因此他们不应该引入任何“事故”,如上所述。

答案 1 :(得分:2)

是。这是完全不可改变的。或者更确切地说,它的实例是完全不可变的。

当然可以有不可变的子类......但这不会影响Animal类实例的可变性。

答案 2 :(得分:2)

编写不可变类很容易。如果满足以下所有条件,则类将是不可变的:

1 All of its fields are final
2 The class is declared final
3 The this reference is not allowed to escape during construction
4 Any fields that contain references to mutable objects, such as arrays, collections, or mutable classes like Date:
    4.1 Are private
    4.2 Are never returned or otherwise exposed to callers
    4.3 Are the only reference to the objects that they reference
    4.4 Do not change the state of the referenced objects after construction

答案 3 :(得分:2)

不,您的Animal类不是不可变的,因为它允许子类化。

为什么不呢?

请参阅此示例子类:

public class ExceptionalAnimal extends Animal {

    public ExceptionalAnimal() {
        super(null);
    }


    @Override
    public String getName() {
        throw new AssertionError("Oops.. where did that come from?");
    }
}

为什么这很重要?

不变性通常用于保证:

  1. 构造后对象的状态不会改变
  2. 该对象是线程安全的
  3. 该对象以某种方式表现
  4. 如果一个类允许子类化,则不能依赖这些保证。如果你有一个接受Animal作为参数的方法,任何人都可以传入一个破坏这些保证的子类。

    修复:没有公开或受保护的构造函数

    一种常用的技术是没有任何publicprotected构造函数。这可以防止从包外部进行子类化,在包中,您仍然可以拥有自己的内部子类。如果课程为final,则不能。

    Google的Guava库中的不可变集合类使用这种技术。 From the Javadoc

      

    虽然这个类不是最终的,但它不能被子类化,因为它没有   公共或受保护的建设者。因此,这种类型的实例是   保证不变。

    客户可以使用静态ImmutableListof()方法创建copyOf

    另见Effective Java,第4项:使用私有构造函数强制执行非实例化。

    请注意,通过不使用publicprotected构造函数来保证不变性比创建类final更容易破解。例如,Mockito将允许您默认模拟这些情况:

    ImmutableList notSoImmutable = mock(ImmutableList.class)
    when(notSoImmutable.size()).thenThrow(new AssertionError("Oops.. where did that come from?"));
    

    关于包私有类

    由于您的Animal类是包私有,因此无法在其包外创建它的子类。因此,假设在它的包中只创建Animal的子类,尊重它的契约,该类实际上是不可变的。

    对于我的回答,我认为Animalpublic。如果您对包私人课程感兴趣,请忽略我的答案; - )

答案 4 :(得分:0)

不可变对象只是在构造之后状态(对象的数据)不能改变的对象。

是的,这是一个完全不可改变的......

答案 5 :(得分:0)

只要您的示例中没有公共setter方法,您的类的对象就是不可变的。

在Java中,可以使用Reflection API绕过它,因为它允许修改各种字段。

final 修饰符放在类上意味着它不能被子类化