为什么我不应该使用不可变的POJO而不是JavaBean?


79

我现在已经实现了一些Java应用程序,到目前为止仅实现了桌面应用程序。我更喜欢使用不可变对象在应用程序中传递数据,而不是使用带有变量(setter和getter)的对象,也称为JavaBeans。

但是在Java世界中,使用JavaBeans似乎更为常见,而且我不明白为什么应该使用JavaBeans。就个人而言,如果代码仅处理不可变的对象而不是始终改变状态,则代码看起来会更好。

项目15:最小化可变性有效的Java 2ed中,还建议使用不可变的对象。

如果我有一个Person实现为JavaBean的对象,它将看起来像:

public class Person {
    private String name;
    private Place birthPlace;

    public Person() {}

    public setName(String name) {
        this.name = name;
    }

    public setBirthPlace(Place birthPlace) {
        this.birthPlace = birthPlace;
    }

    public String getName() {
        return name;
    }

    public Place getBirthPlace() {
        return birthPlace;
    }
}

Person实现为不可变对象:

public class Person {
    private final String name;
    private final Place birthPlace;

    public Person(String name, Place birthPlace) {
        this.name = name;
        this.birthPlace = birthPlace;
    }

    public String getName() {
        return name;
    }

    public Place getBirthPlace() {
        return birthPlace;
    }
}

或更接近structC:

public class Person {
    public final String name;
    public final Place birthPlace;

    public Person(String name, Place birthPlace) {
        this.name = name;
        this.birthPlace = birthPlace;
    }
}

我还可以在不可变对象中使用吸气剂来隐藏实现细节。但是由于我只将它用作一个,struct所以我倾向于跳过“获取器”,并使其保持简单。

简而言之,我不明白为什么使用JavaBeans更好,还是我可以并且应该继续使用我不变的POJO?

许多Java库似乎对JavaBeans都有更好的支持,但是随着时间的推移,对不可变POJO的更多支持可能会越来越流行?


1
我认为您的意思是项目15:最小化可变性,而不是*最小化不变性”
matt b 2010年

@matt:谢谢=)我已经更新了。
乔纳斯(Jonas)2010年

7
您的不变性方法很棒,可以节省许多线程问题。+1
whiskeysierra

1
如果使用JPA,则需要默认的构造函数
Neil McGuigan

Answers:


78

首选JavaBeans

  • 您必须与期望他们的环境进行交互
  • 您有很多属性,在实例化时进行所有初始化将很不方便
  • 您的状态由于某些原因而昂贵或无法复制,但需要更改
  • 您认为在某个时候您可能必须更改属性的访问方式(例如,从存储的值转换为计算的值,访问授权等)
  • 您想遵守无意识地坚持认为使用JavaBeans更“面向对象”的编码标准

首选不可变POJO

  • 你有一些简单的属性
  • 您不必与假定JavaBean约定的环境进行交互
  • 克隆对象时很容易(或者至少是可能的)复制状态
  • 您根本不打算克隆对象
  • 您非常确定不必像上面那样修改属性的访问方式
  • 您不必介意抱怨(或嘲笑)您的代码如何不够“面向对象”

列表+1,您的名字几乎-1 :)毕竟,每个人都知道我的观点是正确的。当然,每个人都是在Discworld中定义的。
Extraneon 2010年

12
边界检查和输入验证可以在构造函数中完成。如果输入无效或超出范围,则我不想创建一个对象。
乔纳斯(Jonas)2010年

24
关于bean的第二点不是支持bean,而是支持Builder模式。您仍然可以构造一个不变的对象。

4
您没有提到使用不可变的POJO进行线程化,这是迄今为止使用它们的最大原因之一。另一个重要的项目是简单性-当对象不变时,从长远来看,使用起来会更容易。同样,对于不可变的POJO,您通常不必担心克隆对象,只需将其传递即可。
威慑2011年

如何构建可能具有循环引用的对象图,以使这些对象不可变?这可能吗?例如,我希望类A和B是不可变的,但A需要引用B,而B需要引用A。有没有办法做到这一点?
chakrit

39

令我惊讶的是,在此讨论中,“线程”一词没有出现。

不变类的主要优点之一是,由于没有可变的共享状态,它们本质上是线程安全的。

这不仅使您的编码更容易,而且还为您带来了两个性能优势:

  • 较少需要同步。

  • 使用最终变量的范围更大,这可以促进后续的编译器优化。

我实际上是在尝试使用不可变对象,而不是JavaBean样式类。通过getter和setter公开对象的胆量可能不是默认选择。


18

好吧,这取决于您要做什么。如果您使用的是持久层,并且您从数据库中提取了一些行到POJO中,并且想要更改属性并将其保存回去,那么使用JavaBean样式会更好,特别是如果您有很多属性。

考虑到您的人有很多字段,例如名字,中间名,姓氏,出生日期,家庭成员,教育程度,工作,薪水等。

并且该“人”恰好是刚结婚并接受改名的女性,因此您需要更新数据库。

如果您使用不可变的POJO,则获取代表她的Person对象,然后创建一个新的Person对象,将所有未更改的属性原样传递给新的Person对象,并保存新的姓氏。

如果它是Java bean,则可以执行setLastName()并保存它。

它是“最小化可变性”,而不是“从不使用可变对象”。在某些情况下,可变对象可以更好地工作,确定可变对象是否更适合您的程序确实是您的工作。您不应该总是说“必须使用不可变的对象”,而应该在伤害自己之前先查看可以使不可变的类数。


1
但是您的示例几乎绝不是“实际”应用程序中需要做的事情。通常,您需要将更新后的数据对象传递到验证框架(Hibernate Validator,Spring Validator等),在该框架中,对象将经受可能由不同开发人员编写的各种操作。如果对象是可变的,则您不知道您是否正在使用传入的相同数据(如果在验证之前执行了某些验证,则会使对象变位)。它使验证过程的决定性降低,更加依赖严格的顺序,并且是不可比拟的。
dsmith

此外,“实际”应用程序可能会具有包含您的域对象的某种内存中或分布式缓存(EHCache等)。如果您知道对象是不可变的,则无需承担读取时复制的开销。您可以安全地检索和使用内存中对象,而不必担心高速缓存的完整性。恕我直言,对于这种业务领域对象,您总是需要不可变的对象。可变对象可以在本地范围内使用,也可以使用其他一些特殊情况,因为我没有这些字符。
dsmith

7

总结其他答案,我认为:

  • 不变性提高了正确性(结构可以通过引用传递,并且您不知道有任何错误/恶意客户端会破坏任何结构)和代码简单
  • 可变性促进了同质性:Spring和其他框架创建了一个没有参数的对象,设置了对象属性和voilà。还可以使用相同的类来提供数据和保存修改,从而使接口更容易(您不需要get(id): Clientsave(MutableClient),因为MutableClient是Client的某些后代。

如果有一个中间点(创建,设置属性,使不可变),框架可能会鼓励采用更多的不可变方法。

无论如何,我建议在不可变对象中将其视为“只读Java Bean”,以强调一点,如果您是个好孩子,并且不碰那个危险的setProperty方法,那么一切都会很好。


我同意,两全其美的做法是使图书馆和框架转向不变的方法。不可变的优点是内在的,而可变的优点是偶然的。
dsmith

3

在Java 7中,您可以拥有不变的bean,这是两全其美的。在构造函数上使用注释@ConstructorProperties。

public class Person {
    private final String name;
    private final Place birthPlace;

    @ConstructorProperties({"name", "birthPlace"})
    public Person(String name, Place birthPlace) {
        this.name = name;
        this.birthPlace = birthPlace;
    }

    public String getName() {
        return name;
    }

    public Place getBirthPlace() {
        return birthPlace;
    }
}


1

老实说,我认为不可变的对象不会如此流行。

我确实看到了优点,但是像Hibernate和Spring这样的框架目前非常流行(并且也有充分的理由),它们确实与Bean一起使用效果最佳。

因此,我认为不变性不是很糟糕,但是肯定会限制您与当前框架的集成选项。

编辑这些评论促使我澄清我的答案。

毫无疑问,在某些问题领域,不变性非常有用,并且确实可以使用。但是我认为当前的默认值似乎是可变的,因为这是大多数人所期望的,并且只有在具有明显优势的情况下才是不可变的。

尽管确实可以在Spring中使用带参数的构造函数,但它似乎是一种与旧的和/或第三方代码一起使用美丽的全新Spring代码的方法。至少那是我从文档中挑选的。


2
IoC / DI的思想基于设置/注入状态,这就是为什么不变性在Spring中不那么流行的原因。但是Joda-Time是正确完成不变性的一个很好的例子。
lunohodov

您也可以使用构造函数args通过spring注入依赖项,似乎这样做的人并不多(可能是因为当您有很多依赖项时,拥有10个以上参数的构造函数很可怕)。
Andrei Fierbinteanu

6
@lunohodov:我已经将Google Guice用于DI / IoC,并且对构造函数进行依赖注入没有问题。
乔纳斯(Jonas)2010年

通常,如果使用不可变一次创建的对可变对象的引用或对不可变对象的可变引用来存储数据,则最容易处理事物(尤其是在多线程环境中)。对可变对象的自由可变的引用使事情变得更加复杂。请注意,如果XY指向同一个对象并且要更改X.something,则可以保留X的标识并进行更改Y.something,或者可以保持Y.something不变并更改X的标识,但是在更改时不能保留X的标识和Y的状态。 X州
超级猫

那些推动使用不可变对象的人有时无法认识到对象身份的重要性,而可变对象可以以不可变对象无法拥有的方式具有不可变身份这一事实。
2013年

0

就Java编程而言,它是不变的:一旦创建,就永远不会发生状态更改,无论是预期的还是意外的!

此技术在防御性编程中非常有用,在防御性编程中,另一个实体无法引发状态变化。

您不希望更改的示例:外部系统(单线程或多线程),它从您的层获取引用并在该对象上运行,并且有意或无意地对其进行了更改。现在它可能是POJO或集合或对象引用,并且您不希望更改或希望保护数据。您肯定会将对象作为防御技术变得不可变

您期望更改的示例:确实不需要不变性,因为它将在正确的编程过程中受阻。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.