6.为何加了final仍然无法拥有"不可变性"?

219 阅读3分钟

1.final修饰对象只是引用不可变,对象本身可以变

class Test {
    int p=20;
    public static void main(String[] args) {
        Test test = new Test();
        test.p=10;
        System.out.println(test.p);
    }
}

2.final和不可变的关系

关键字 final 可以确保变量的引用保持不变,但是不变性意味着对象一旦创建完毕就不能改变其状态 ,它强调的是对象内容本身,而不是引用,所以 final 和不变性这两者是很不一样的。

对于一个类的对象而言,你必须要保证它创建之后所有内部状态(包括它的成员变量的内部属性等)永远不变,才是具有不变性的,这就要求所有成员变量的状态都不允许发生变化。 有一种说法就认为:“要想保证对象具有不变性的最简单的办法,就是把类中所有属性都声明为 final”,这条规则是不完全正确的,它通常只适用于类的所有属性都是基本类型的情况,比如前面的例子:

​
class Person {
    final int id = 1;
    final int age = 19;
}

Person 类里面有 final int id 和 final int age 两个属性,都是基本类型的,且都加了 final,所以Person 类的对象确实是具备不变性的。 但是如果一个类里面有一个 final 修饰的成员变量,并且这个成员变量不是基本类型,而是对象类型,那么情况就不一样了。有了前面基础之后,我们知道,对于对象类型的属性而言,我们如果给它加了final,它内部的成员变量还是可以变化的,因为 final 只能保证其引用不变,不能保证其内容不变。所以这个时候若一旦某个对象类型的内容发生了变化,就意味着这整个类都不具备不变性了。 所以我们就得出了这个结论:不变性并不意味着,简单地使用 final 修饰所有类的属性,这个类的对象就具备不变性了。

那就会有一个很大的疑问,假设我的类里面有一个对象类型的成员变量,那要怎样做才能保证整个对象是不可变的呢? 我们来举个例子,即一个包含对象类型的成员变量的类的对象,具备不可变性的例子。

package com.test;
​
import java.util.HashSet;
import java.util.Set;
​
public class ImmutableDemo {
    private final Set<String> lessons = new HashSet<>();
​
    public ImmutableDemo() {
        lessons.add("1");
        lessons.add("2");
        lessons.add("3");
    }
​
    public boolean isLesson(String name) {
        return lessons.contains(name);
    }
}

在这种情况下,尽管 lessons 是 Set 类型的,尽管它是一个对象,但是对于 ImmutableDemo 类的对象而言,就是具备不变性的。因为 lessons 对象是 final 且 private 的,所以引用不会变,且外部也无法访问它,而且 ImmutableDemo 类也没有任何方法可以去修改 lessons 里包含的内容,只是在构造函数中对 lessons 添加了初始值,所以 ImmutableDemo 对象一旦创建完成,也就是一旦执行完构造方法,后面就再没有任何机会可以修改 lessons 里面的数据了。而对于 ImmutableDemo 类而言,它就 只有这么一个成员变量,而这个成员变量一旦构造完毕之后又不能变,所以就使得这个ImmutableDemo 类的对象是具备不变性的,这就是一个很好的 “包含对象类型的成员变量的类的对象,具备不可变性” 的例子