可变或不可变的类?

我在一些设计书中读到,不可变类提高了可伸缩性,并且尽可能地编写不可变类的良好实践。 但我认为这样不可改变的阶级会增加对象的扩散。 因此,为了提高可伸缩性,继续使用不可变类或更好的静态类(具有所有静态方法的类)是不是很好?

不可变类确实会促进对象扩散,但是如果你想要安全,可变对象会促进更多的对象扩散,因为你必须返回副本而不是原始副本以防止用户更改你返回的对象。

至于使用所有静态方法的类,在大多数可以使用不变性的情况下,这不是一个真正的选项。 从RPG中获取此示例:

public class Weapon { final private int attackBonus; final private int accuracyBonus; final private int range; public Weapon(int attackBonus, int accuracyBonus, int range) { this.attackBonus = attackBonus; this.accuracyBonus = accuracyBonus; this.range = range; } public int getAttackBonus() { return this.attackBonus; } public int getAccuracyBonus() { return this.accuracyBonus; } public int getRange() { return this.range; } } 

您如何使用仅包含静态方法的类实现此操作?

但是, 不可变类的主要好处是可以公开不可变的内部数据成员,因为调用者无法修改它们。 这是一个巨大的问题,比如java.util.Date 。 它是可变的,因此您无法直接从方法返回它。 这意味着你最终会做各种防御性复制 。 这增加了对象的扩散。

另一个主要好处是根据定义,不可变对象没有同步问题。 这就是可扩展性问题的来源。编写multithreading代码很难。 不可变对象是(大多数)绕过问题的好方法。

至于“静态类”,通过你的评论,我认为它是指具有工厂方法的类,这是它通常描述的方式。 那是一种无关的模式。 可变类和不可变类都可以具有公共构造函数或具有静态工厂方法的私有构造函数。 这对类的(im)可变性没有影响,因为可变类是在创建后可以更改其状态的类,而在实例化后不能更改不可变类的状态。

然而,静态工厂方法可以具有其他好处。 我们的想法是封装对象创建。

正如cletus所说,不可变类简化了同步方法中的类设计和处理。

即使在单线程应用程序中,它们也简化了集合中的处理。 不可变类永远不会改变,因此密钥和哈希码不会改变,所以你不会搞砸你的集合。

但是你应该记住你正在建模的东西的生命周期和构造函数的“重量”。 如果您需要更改该东西,则不可变对象变得更加复杂。 您必须替换它们,而不是修改它们。 并不可怕,但值得考虑。 如果构造函数需要非常重要的时间,那也是一个因素。

需要考虑的一件事是:如果您打算在HashMap中使用类的实例作为键,或者如果您要将它们放在HashSet中,那么使它们不可变是更安全的。

HashMap和HashSet指的是只要对象在map或set中,对象的哈希码就保持不变。 如果你将一个对象用作HashMap中的一个键,或者你把它放在一个HashSet中,然后改变对象的状态,以便hashCode()返回一个不同的值,那么你就会混淆HashMap或HashSet,你会得到奇怪的东西; 例如,当您迭代地图或设置对象时,但是当您尝试获取它时,就好像它不存在一样。

这是由于HashMap和HashSet如何在内部工作 – 它们通过哈希代码组织对象。

Java并发大师Brian Goetz 撰写的这篇文章很好地概述了不可变对象的优缺点。

不变性通常用于实现可伸缩性,因为不可变性是Java中并发编程的推动因素之一。 因此,正如您所指出的那样,“不可变”解决方案中可能存在更多对象,这可能是提高并发性的必要步骤。

另一个同样重要的使用不可变性是消耗设计意图 ; 谁创造了一个不可变类,意味着你把可变状态放在别处。 如果你开始改变那个类的实例,你可能会破坏设计的初衷 – 谁知道后果可能是什么。

以字符串对象为例。 某些语言或类库提供可变字符串,有些则不提供。

使用不可变字符串的系统可以进行某些优化,使用可变字符串的系统不能。 例如,您可以确保只有一个唯一字符串的副本。 由于对象“开销”的大小通常远小于任何非平凡字符串的大小,因此这可能节省大量内存。 还有其他潜在的空间节省,如实习子串。

除了可能的内存节省之外,不可变对象还可以通过减少争用来提高可伸缩性。 如果您有大量线程访问相同的数据,则不可变对象不需要精心设计的同步过程来进行安全访问。

再考虑一下这个问题。 使用不可变对象允许您缓存它们而不是每次都重新创建它们(即字符串)它对您的应用程序性能有很大帮助。

我想,如果你想在不同的变量之间共享同一个对象,它需要是不可变的。

例如:

 String A = "abc"; String B = "abc"; 

Java中的String对象是不可变的。 现在A和B都指向相同的“abc”字符串。 现在

 A = A + "123"; System.out.println(B); 

它应该输出:

 abc 

因为String是不可变的,所以A只会指向新的“abc123”字符串对象,而不是修改前一个字符串对象。