不可变类需求方案

时间:2014-04-27 18:16:51

标签: java java-ee

我最近理解了不可变类的概念。我也理解了如何使类不可变的概念。但我只是想知道在我们的Java Project实现中是否有任何特定的场景,其中绝对需要使用不可变类。换句话说,是否存在必须使用用户定义的不可变类的情况?

1 个答案:

答案 0 :(得分:0)

我认为,有两个主要原因可能是您想要使用不变性。一种是简化程序执行的推理,二是使并发编程更有效,更容易。

我将首先介绍一种对象如何在Java中不可变的方法。

一个是有效的不可变对象,一旦构造就不会修改它的状态。

第二种类型是一个在Java内存模型和并发编程保证方面不可变的对象,我的意思是这里的一个对象,其所有字段都标记为final,并且其this引用不是在对象构造期间逃逸。

执行原因

两种不变性类型都具有使程序更易于推理的特性。例如,使用可变对象作为Map中的键似乎不是一个好主意。考虑一个类存储,它可以保持用户的年龄。

class Storage {
    private Map<User, Integer> age = new HashMap<>();

    public void put(User name, Integer userAge) {
        age.put(name, userAge);
    }

    public Integer get(User name) {
        return age.get(name);
    }

}

用户类是可变的。

class User {
    private String name;

    User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void toUpperCase() {
        name = name.toUpperCase();
    }

    // consider hashCode and Equals to be correctly 
    // implemented and delegate to `name`

}

很容易实现您无法再找到用户年龄的情况。

    Storage storage = new Storage();

    User john = new User("John");
    storage.put(john, 25);

    System.out.println(storage.get(new User("John")));

    // we modify the reference which is used as a key in our map
    john.toUpperCase(); 
    System.out.println(john.getName());

    System.out.println(storage.get(new User("John")));

这是一个相当简单的案例,很容易发现错误,但在复杂的程序中,这可能会导致难以跟踪的问题。一种解决方案是制作对象的防御性副本,但要使它们不变是更容易的。

并发编程

我在开始时提到过,在处理并发时,不可变对象很有用。一旦安全发布,您就不必同步对不可变对象的访问。没有可变状态,因此没有竞争条件。这可能会带来更好的性能和更少杂乱的代码。

此外,将所有字段设为final将为您提供happens-before语义。例如。如果你有一个ImmutableUser类的有效不可变对象,那么由于how Java Memory Model works,其他线程可能会看到address字段的不正确值。

class Address {
    private final String street;

    Address(String street) {
        this.street = street;
    }

    // getters
}

class ImmutableUser {
    private Address address;

    ImmutableUser(String street) {
        this.address = new Address(street);
    }

     // getters
}

标记address字段final会在保证之前发生 - 这意味着一旦构造并安全发布ImmutableUser,所有字段都将具有所有线程看到的正确值(使地址易变或添加同步也可以,并且对象将保持有效不变。