分类
Java

什么是POJO类

What is a POJO Class?

1. 概述

本文中,我们将讨论什么是POJO(Plain Old Java Object)。并将POJO与JavaBean做一个简单的比较,阐述为何以及如何将POJO转换为JavaBean。

2. Plain Old Java Object

2.1 什么是POJO

从字面意思上来看。P = plain,译为简单的,直白的;O = old,译为老的。综合来看笔者认为可以如下理解:一眼就能看明白的常规Java对象。维基百科上如下解释:In software engineering, a plain old Java object (POJO) is an ordinary Java object, not bound by any special restriction. The term was coined by Martin Fowler, Rebecca Parsons and Josh MacKenzie in September 2000(在软件工程领域,POJO是一个普通的Java对象,其不受任何特殊的限制。该术语由Martin Fowler, Rebecca Parsons and Josh MacKenzie在2000年提出)。

只所以说它简单、直白、老,是由于POJO具有以下特点:

  • ➊ 不继承任何类
  • ➋ 不实现任何接口
  • ➌ 无任何注解
  • ➍ 不依赖于任何第三方框架(库)

比如我们创建如下POJO:

// ➌
public class EmployeePojo➊ {
 
    public String➋ firstName;
    public String➋ lastName;
    private LocalDate➋ startDate;
 
    public EmployeePojo(String firstName, String lastName, LocalDate startDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.startDate = startDate;
    }
 
    public String name()➍ {
        return this.firstName + " " + this.lastName;
    }
 
    public LocalDate getStart()➍ {
        return this.startDate;
    }
}
  • ➊ 无继承、无实现
  • ➋ 属性类型均为java内置类型
  • ➌ 无注解

由于该类未依赖于任何第三方框架(库),所以可以被任何的Java程序使用,这是POJO最明显的优点。而类中的方法命名➍也可以比较随性,这就不太好了。

正是由于这种随性的方法命名➍方式,无意间增加了在交流上的成本:

首先,其它的程序员需要花更多的时间来理解该无规则命名的类,而当这样的类越来越多时,对程序员来讲无疑是一个恶梦。

其次,其它框架无法自动理解、使用该无规则命名的类,这也就导致了无法将此类直接应用到现在框架(库)上。

下面我们使用代码来展示下一般框架是如何利用反射机制来获取类的属性信息,以及为何随性命名的POJO无法很好地配合框架工作的。

2.2 反射POJO

我们添加一个commons-beanutils到当前项目中:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

然后使用如下方法来反射EmployeePojo的属性:

class EmployeePojoTest {
    @Test
    void inspectProperties() {
        // 反射EmployeePojo的属性
        List<String> propertyNames =
                Arrays.stream(PropertyUtils.getPropertyDescriptors(EmployeePojo.class))
                        .sequential()
                        .map(PropertyDescriptor::getDisplayName)
                        .collect(Collectors.toList());

        // 打印反射的结果
        System.out.println(propertyNames);

        // 打印的结果总归还需要眼睛来看,而使用断言则不需要眼睛来看控制台,且更规范
        assertEquals(1, propertyNames.size());
        assertEquals("start", propertyNames.get(0));
    }
}

运行测试代码,最终控制台打印如下:

[start]

可见对EmpoyeePojo进行反射后并获取到其它两个属性:firstName、lastName。

这并不是由于我们使用了commons-beanutils造成的,使用其它的库(比如:Jackson)来反射EmpoyeePojo,也会得到同样的结果。

我们希望对EmpoyeePojo的反射能够获取到所有的字段:firstName、lastName以及startDate,这对框架而言非常的有必要。虽然一般的POJO无法实现上述功能,但值得庆幸的是大多数的框架(库)均支持一种叫做JavaBean的命名规范,若想反射出所有的字段则只需要简单的遵循该规范即可。

3. JavaBeans

3.1 什么是JavaBean

JavaBean原则上也是一个POJO,相较于POJO它还需要遵循以下规范:

  • 访问级别:所有的属性需要定义为private类型,并且提供getter以及setter方法。
  • 方法命名:getter、setter方法必须以getX以及setX的命名规范(如果字段类型为boolean,则getter方法还可命名为:isX)。
  • 默认构造函数:必须提从一个没有任何参数的构造函数。因为只有这样才能够在不提供任何参数的情况下创建对应的实例,最经典的使用场景为:反序列化。
  • 可(反)序列化:必须实现Serializable接口(该接口中未定义任何方法,作用仅为标识class是否可(反)序列化。

3.2 将EmployeePojo转换为JavaBean

最终我们将EmployeePojo转换为如下JavaBean:

public class EmployeeBean implements Serializable➍ {
    private static final long serialVersionUID = -3760445487636086034L;➍
    private➊ String firstName;
    private➊ String lastName;
    private➊ LocalDate startDate;

    public EmployeeBean()➌ {
    }

    public EmployeeBean(String firstName, String lastName, LocalDate startDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.startDate = startDate;
    }

    public String getFirstName()➋ {
        return firstName;
    }

    public void setFirstName(String firstName)➋ {
        this.firstName = firstName;
    }

    public String getLastName()➋ {
        return lastName;
    }

    public void setLastName(String lastName)➋ {
        this.lastName = lastName;
    }

    public LocalDate getStartDate()➋ {
        return startDate;
    }

    public void setStartDate(LocalDate startDate)➋ {
        this.startDate = startDate;
    }
}
  • ➊ 访问级别
  • ➋ 方法命名
  • ➌ 空构造函数
  • ➍ 实现接口

3.3 反射JavaBean

使用同样的代码来反射EmployeeBean,则得到如下结果:

[firstName, lastName, startDate]

4. 权衡利弊

虽然JavaBeans在很多时候为我们带来了较大的便利,但其缺点也很明显,这需要我们在使用时结合其优缺点来权衡利弊。

JavaBeans的主要缺点如下:

  • 易变性:由于我们需要为每个字段都设置一个setter方法,所以原则上每个字段都可以通过setter方法来重新赋值。这有时候会引发一些并发或数据不一致的问题。
  • 死板:我们不得不为每个字段都提供getter/setter方法,而有些时候我们根本用不到某些getter/setter,但却不得不收书定大量样板化的代码。
  • 空构造函数:由于某些对象在实例化时必须传入相应的字段才会有意义,所以我们可以在构造函数中声明必须传入的字段从而达到约束特殊字段的目的。而此时我们提供的空构造函数破坏了这一约束性。

但值得庆幸的事,大多数的框架已经习惯并适应了JavaBean,并能够很好的对其缺点进行处理。

5. 总结

本文中我们对POJO进行了解读,给出了POJO的定义堆满,并给出了相应的事例。通过反射测试来说明POJO具有的天生缺点,随后引入了JavaBean的规范、定义,并通过实例、反射测试来说明JavaBean的优势。凡事都具有两面性,在文章的最后,我们列举了JavaBean的缺点。