返回

Java POJO详解:定义、使用场景及最佳实践

java

啥是 POJO?别再被“普通”给忽悠了!

经常听到“POJO”这个词儿,但真要解释起来,又觉得模模糊糊? 维基百科上说它是“普通的 Java 对象”,不是什么“特殊对象”。 那么问题来了,Java 对象里,啥样的算“特殊”?啥样的又算“普通”呢?这篇文章就来掰扯掰扯这个问题。

维基上还提到,POJO 不应该继承预定义的类,实现预定义的接口,或者包含预定义的注解。这听起来限制挺多的,那像 SerializableComparable 这种常用的接口,或者咱们自己写的类和接口,POJO 还能用吗? 更进一步,这个“不继承、不实现”的规矩,是不是意味着连外部库都不能用了? POJO 到底用在哪儿呢?别急,咱们慢慢来。

一、 “特殊”对象 VS “普通”对象

要搞清楚 POJO,得先弄明白 Java 眼里的“特殊”对象是啥。 咱们不妨反过来想,一个对象如果背负了太多“额外”的职责,那它就“特殊”了。

啥叫“额外”的职责?举几个例子:

  • 被框架强行绑定: 比如,一个对象必须继承某个框架指定的父类 (例如早期的 EJB),或者必须实现框架规定的接口才能工作,这就算是“特殊”了。
  • 被容器严格管理: 有些对象,它的生命周期、行为方式都受到容器的严格控制,甚至连创建和销毁都不能自己做主(还是拿 EJB 举例),那也挺“特殊”的。
  • 添加了特定注解 : 假设对象因为项目需要增加了某个特殊框架的注解才能继续下一步业务的话,那也变得"特殊"了

那“普通”对象呢? 就是那些没这么多条条框框,干干净净的对象。 它就是一个纯粹的数据载体,只负责存储数据,以及提供一些简单的 getter/setter 方法来访问这些数据。

二、POJO 的“清规戒律”,到底有多严?

维基上说的那些“不应该”,其实是为了强调 POJO 的“纯粹性”。 但在实际开发中,我们还是要灵活变通的。

1. 能不能实现接口?

当然可以! 像 SerializableComparable 这些接口,它们并不会给 POJO 增加额外的负担,反而能提供一些很有用的功能(比如序列化、排序)。

甚至,咱们自己定义的接口,POJO 也可以实现。只要这个接口不强迫 POJO 去做一些“份外”的事情就行。

2. 能不能继承类?

这个要看情况。

  • Java 标准库里的类: 一般来说,没问题。 比如,你完全可以继承 ArrayList 来创建一个你自己的列表类。
  • 第三方库里的类: 也 OK,只要这个库的设计比较合理,不强迫你的 POJO 做出什么奇怪的改变。
  • 框架相关的类: 这个就要小心了。 尽量避免直接继承框架提供的基类,因为这很可能会让你的 POJO 变得“特殊”。

3. 能不能用注解?

要具体情况具体分析!。

有些注解,比如 @Override@Deprecated,它们只是给编译器提供一些额外的信息,不会影响 POJO 的“纯粹性”。

但有些注解,比如 JPA 里的 @Entity@Table,它们会改变对象的行为,甚至会把对象跟特定的框架(这里是 JPA)紧紧绑定在一起。 这时候,这个对象就不再是 POJO 了。

4. 能不能用外部库?

当然可以!

POJO 的“不继承、不实现” 主要是针对框架而言的,并不是禁止一切外部库。 只要这些库不会给 POJO 带来“额外”的负担。比如,你可以放心地使用 Apache Commons Lang、Google Guava 这些工具库。

代码示例

我们来具体看看一个User 类怎么定义

public class User {

    private Long id;
    private String username;
    private String password;
     // 标准库完全ok.
    private Date createTime;
    // 自定义数据
    private  List<Role> roles;
    public User() {
    }

    public User(Long id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
    
      @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", createTime=" + createTime +
                    ", roles=" + roles +
                    '}';
    }    

    // ... getter/setter 方法 ...
}

安全建议: 尽量避免在 POJO 中存储敏感信息(比如密码的明文)。 如果确实需要存储,要采取适当的加密措施。同时注意 roles 这个 list , 也尽量保证是一个简单干净的数据对象。

三、POJO 的“用武之地”

POJO 的核心价值在于它的“简单”。 因为简单,所以灵活,所以适用范围广。

  • 数据传输: 在不同层、不同系统之间传递数据,POJO 是一个很好的选择。 比如,你可以用一个 POJO 来封装从数据库查询出来的结果,然后把它传递给前端页面。
  • 业务逻辑: 很多业务逻辑其实都可以用 POJO 来表达。 比如,你可以用一个 POJO 来表示一个订单,然后在这个 POJO 上定义一些方法来计算订单金额、检查订单状态等等。
  • 领域建模: 在领域驱动设计(DDD)中,POJO 可以用来表示领域模型中的实体和值对象。 这时候,POJO 不再仅仅是一个数据载体,它还承载了领域逻辑。
  • 解耦 : 不同组件间使用POJO 进行数据传递,避免直接依赖。 这样能提高系统的灵活性和可维护性。

进阶技巧

  1. 不可变 POJO: 如果 POJO 的数据不需要修改,可以考虑把它设计成不可变的。 这样可以避免很多并发问题,也能提高代码的可读性。通过构造器一次性初始化,并且所有字段设置 final
  2. Builder 模式: 如果 POJO 的字段比较多,可以考虑使用 Builder 模式来创建对象。 这样可以让代码更清晰,也能避免“长参数列表”的问题。
  3. Lombok : Lombok 工具包可以通过注解自动生成 getter, setter, constructor等方法,极大简化 POJO代码编写.

总之,POJO 并不是什么高深的概念,它就是一个简简单单的 Java 对象。 但就是因为这份简单,让它在各种场景中都能发挥作用。