2011-11-23 46 views
17

我发现一个article一个有趣的一段代码:Java不可变类?

public class Employee { 

    private String firstName; 
    private String lastName; 

    //private default constructor 
    private Employee(String firstName, String lastName) { 
     this.firstName = firstName; 
     this.lastName = lastName; 
    } 

    public static Employee valueOf (String firstName, String lastName) { 
     return new Employee(firstName, lastName); 
    } 
} 

我理解产生这种课的优势真的很好奇。 我明白在这里,这个类的一个对象将是不可变的,因为一旦初始化就无法改变它的变量值。我从来没有做过这样的事情,我真的不明白它的优点。

  • 为什么这是一个好的做法呢?
  • 难道你的名字在那里可以使用这种方法的情况吗?
  • 常数或只读变量呢?这不是很相似吗?
  • 在文章中说,这是不利于应用程序的性能。 但为什么
+5

可变状态使得很难推断代码的作用。研究函数式编程。 –

+0

该类没有访问器方法。这是故意的吗? –

+0

有帮助的文章,如果你还没有阅读它:http://www.javapractices.com/topic/TopicAction.do?Id=29 –

回答

18

您提到的例子是Immutable Objects。其广泛使用的概念在programming languages

从上面的链接引用。其优点是

  • 是简单的构建,测试和使用
  • 是自动线程安全的,并没有同步问题
  • 不需要拷贝构造函数
  • 不需要克隆的实现
  • 允许的hashCode使用延迟初始化,并缓存它的返回值
  • 作为现场使用时不需要被防守复制
  • 好好地图键和SET元件(这些对象不得在集合中改变状态的同时)
  • 有自己的班级不变的构造时一旦建立,它永远不需要再次检查
  • 始终有“失败原子”(由Joshua布洛赫所使用的术语):如果一个不可变对象 - 抛出一个异常,它从来不期望的或不确定的状态
+0

好的工作列出了优势。我只想补充一点,员工类通常不是以一种不可变的方式实现的,因为当时不需要上述任何一点,但共享可变状态可以更容易地保持员工的所有视图一致,即对员工的任何修改都将会每个人都可以立即看到它。如果我们必须创建一个新对象来表示已更改的状态,那么旧引用仍然会看到旧状态。有时候这是需要的,有时候是不需要的,更新所有引用来指向新对象是低效和繁琐的。 – meriton

+2

我不确定“允许hashCode使用延迟初始化,并缓存它的返回值”。你的意思是对象的散列码是在第一次调用hashCode()方法时计算和存储在一个字段中的,并且之后会返回该字段?因为在这种情况下,对象实际上是* not *不可变的,并且失去了“自动线程安全”属性。多线程无法同时安全地调用hashCode(),除非他们可以确定该字段之前已经被初始化(或者除非使用'synchronized' /'volatile'/whatnot)。 – ruakh

+0

@Ruakh:是的。我相信即使在这种情况下,也有多种方式可以使它成为多线程安全的,例如,如果您可以自动确定哈希代码尚未计算出来,然后在初始化哈希代码时计算并返回正确的值,那么你知道对于本地执行线程来说,正确的值总是返回的值。您可能会计算两次价值,但我认为您仍然可以始终获得正确的价值。我希望我的“Java并发实践”得心应手,所以我可以查看细节(并确保我是正确的)。 – jprete

4

不可变类:默认情况下

  • 线程安全的(并行写入永远不会发生)
  • 可缓存

您可以在语言的conext读了很多关于他们爪哇在Effective Java

+9

'平等检查可以用== **完成**不正确**。 – Ingo

+0

仅仅因为一个类是不可变的,并不意味着可以用“==”来检查相等性。例如,字符串是不可变的,并且在很多情况下,“==”将不起作用,其中.equals()将会。为了使其发挥作用,需要将其与扩展工厂结合使用,以确保“平等”值不超过一个实例存在。 – ziesemer

+0

好的,我删除了它,但是如果我有对类的全部实例控制,我可以将它们与==进行比较,我的意思是ziesemer写的。 – zeller

2

如果使用哈希表,具有不可变对象,因为你不需要重新计算的hashCode是好当的对象状态变化(因为它们是不可改变的)。

3

不变类的主要优点是线程安全的。线程的大部分问题来自共享,可变状态。通过使对象不可变,特别是在多线程环境中推理它们要容易得多。

文章说,“创建不可变对象可以打一个应用程序的性能。”我不确定它为什么这么说。这是完全错误的。对于不可变对象可能会影响应用程序的性能没有任何固有的。

+0

什么都没有?创建新对象并复制其整个状态与更新单个字段一样快? – meriton

3

- 为什么它是一个很好的做法剩下什么?

因为您可以传递类并确保它永远不会被“流氓”代码修改。对于Java字符串也是如此,它们是不可变的。

- 您是否可以说出可以使用此方法的情况?

这对于许多团队一起工作的大项目或设计框架或API时非常有用。在这些情况下,由于您对代码的某些部分不负责任,您永远不能相信您传递给代码其他部分的对象不会被改变。如果您需要确保对象不会被修改,请使用不变性。

- 关于常量或只读变量?这不是很相似吗?

不是在Java中,因为我们既没有const也没有只读。我们所拥有的是确保对象引用在第一次分配之后不会被修改的最终关键字。但即使引用不能,底层对象仍然可以被修改。不可变类确保创建后对象状态不会被改变。

- 文章中说,这对于应用程序的性能不利。但为什么?

因为每次需要修改对象时,都需要创建新的实例。字符串一样,你不能做myString.append("42"),你需要做myString = myString+"42",它创建一个新的String对象。

+2

除了关于表演的评论之外,我几乎都赞成,因为他们错了。是的,使用不可变对象意味着你分配了更多的对象,但在现代JVM实现中,这应该是闪电般的。 –

+0

是的,分配并不昂贵,但新的运算符不只是分配一个对象,它也调用构造函数。在上面的例子中,字符串连接涉及复制整个字符串的*内容*,对于大型字符串或频繁复制的字符串而言,这会变得非常昂贵。 – meriton

+0

分配一个不可变对象的新副本的代价是部分(或完全!)弥补的,因为您不需要再制作防御副本。我的经验是,价值对象的防御性复制比修改要频繁得多,所以如果对象是不可变的,那么实际上你的副本就少了。 – jprete

1

文章说:

若要使类不可变的,你可以定义它的所有构造私有,然后创建一个公共静态方法来初始化和对象,并返回。

其实,这是错误的。这两个概念并没有真正相关。

E.g.你可以声明你的Employee类的构造函数是公开的,它仍然是不可变的。

或者你可以通过一个可变的对象作为参数传递给工厂方法或声明赋值函数方法

- 尽管你使用的是工厂方法和私人构造>员工将是可变的。

0

在给出的例子中,他将构造函数设置为私有的,从而直接控制从外部创建对象。

含义:由于构造函数是私有的,你不能从这个类之外做

Employee e = new Employee("steve","jobs"); 

通过这样做,这个类的程序员正在将这个类的对象创建纳入到他的控制之中。

当你编写非常庞大的服务器端类时,这是非常有益的,因为创建一个对象可能会因为它的大小而占用大量内存。现在,您如何保护您的客户,避免为您的课堂创造更多对象?

上面的问题的答案很简单,通过使您的构造函数为私有,并且您自己为您的类创建对象,当您需要使用静态方法时。 注意:可以使用类名直接访问静态方法。

注意:这种设计模式将被大量使用在单身设计模式中,对于给定的类只需要一个对象。