Java 非final类可以是完全不可变的吗?

Java 非final类可以是完全不可变的吗?,java,immutability,Java,Immutability,一个没有最终修饰符的类可以是完全不可变的吗 class Animal { private String animalName; public Animal(String name) { animalName = name; } public String getName() { return animalName; } } 例如,下面的类是不可变的吗 class Animal { private String animalName;

一个没有最终修饰符的类可以是完全不可变的吗

class Animal
{
    private String animalName;

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

    public String getName() { return animalName; }
}
例如,下面的类是不可变的吗

class Animal
{
    private String animalName;

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

    public String getName() { return animalName; }
}

对。它是完全不变的。或者更准确地说,它的实例是完全不可变的

当然,可能有一些子类不是不变的。。。但这并不影响
动物类实例的可变性。

类(如您的问题中所描述的)是不可变的,因为它的内部状态不可能更改。即使要定义
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
关键字,因此他们不应引入上述任何“事故”。

不可变对象只是在构造后其状态(对象的数据)无法更改的对象


是的,它是完全不可变的…

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

在Java中,这可以使用反射API绕过,因为它允许修改所有类型的字段


将最后一个修饰符放在类上意味着它不能被子类化

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

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

不,您的动物类不是不变的,因为它允许子类化

为什么不呢?

请参见此示例子类:

public class ExceptionalAnimal extends Animal {

    public ExceptionalAnimal() {
        super(null);
    }


    @Override
    public String getName() {
        throw new AssertionError("Oops.. where did that come from?");
    }
}
为什么这很重要?

不变性通常用于保证:

  • 对象的状态在构造后不会改变
  • 对象是线程安全的
  • 对象以某种方式运行
  • 如果一个类允许子类化,那么这些保证都不能依赖。如果您有一个接受
    动物
    作为参数的方法,任何人都可以传入一个违反这些保证的子类

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

    一种常用的技术是不让任何
    公共
    受保护的
    构造函数。这可以防止从包外部进行子类化,而在包内部,您仍然可以拥有自己的内部子类。如果该类为
    最终版
    ,则不能执行此操作

    Google的Guava库中的不可变集合类使用这种技术:

    虽然这个类不是final,但它不能被子类化,因为它没有 公共或受保护的构造函数。因此,这种类型的实例是 保证是不变的

    客户端可以使用static
    of()
    copyOf
    方法创建
    ImmutableList
    s

    另请参见第4项:使用私有构造函数强制实现不稳定性

    请注意,通过不使用
    public
    protected
    构造函数来保证不变性比使类
    成为final
    更容易破坏。例如,默认情况下,将允许您模拟这些情况:

    ImmutableList notSoImmutable = mock(ImmutableList.class)
    when(notSoImmutable.size()).thenThrow(new AssertionError("Oops.. where did that come from?"));
    
    在包私有类上

    由于您的
    Animal
    类是包私有的,因此不可能在其包之外创建它的子类。因此,假设在它的包中只创建尊重其契约的
    Animal
    的子类,那么这个类实际上是不可变的


    对于我的回答,我假设
    动物
    公共的
    。如果您对包私有类特别感兴趣,请忽略我的回答;-)

    如果所有这些条件都满足,类将是不可变的,但不是所有这些条件都必须满足,类才是不可变的。第三点是什么意思?你能给我指出一个“this”转义的例子吗?@user2434这意味着将对正在构造的对象的引用传递到构造函数之外的某个地方。例如调用
    SomeClass.someMethod(this)
    ,或
    SomeClass.someField=this。无论谁接收到该引用,都可以看到其状态发生变化,因为字段尚未初始化。当您询问某个对象是否是不可变的时,您必须对该对象的使用方式做出一些合理的假设。e、 g.如果使用反射,没有什么是不可变的。如果类的文档指定所有合法派生类必须满足某些条件,那么可以构造违反这些条件的派生类的事实并不意味着不应期望该类的派生类遵守这些条件。例如,
    object
    的文档指定所有派生都应该定义比较和散列码方法,这样比较相等的两个实例将具有相同的散列码。如果类
    foo
    将其hashcode成员定义为仅返回随机数,则将某些内容存储在
    字典中,
    并……尝试查找它可能会失败——这不是因为
    字典
    坏了,而是因为
    foo
    坏了。如果一个对象的文档指定所有合法的实现都必须遵守您的不变性标准,那么为什么这样的对象的用户不能期望在没有恶意的情况下,他们会遵守这些标准呢?@supercat有问题的
    Animal
    类没有这样的文档。也,