JavaBean规范

什么是 JavaBean

JavaBean 就是符合特定约定的普通 Java 类,就像标准化的乐高积木——大家都遵守统一规格,框架才能自动拼装

很多框架(Spring、MyBatis、Jackson等)都依赖这套约定来自动操作你的对象

不是什么高级技术,就是一套写类的规矩

JavaBean 的四大规范

规范 说明 示例
private 字段 所有属性用 private 修饰(参见 访问修饰符 最小权限原则) private String name;
public getter/setter 每个字段提供公开的访问方法 getName() / setName()
无参构造器 必须有一个无参数的构造方法 public User() {}
实现 Serializable 可选但推荐,让对象能序列化传输 implements Serializable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.Serializable;

// 标准的 JavaBean
public class User implements Serializable {
// 1. private 字段
private String name;
private int age;
private String email;

// 2. 无参构造器(如果没写任何构造器,Java 会自动提供一个)
public User() {}

// 有参构造器(方便使用,但不能省掉无参构造器!)
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}

// 3. public getter / setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }

public int getAge() { return age; }
public void setAge(int age) { this.age = age; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}

getter/setter 命名规范

这不是随便取名的,有严格规则,框架靠这个规则通过反射找到你的字段:

字段类型 getter 命名 setter 命名
普通类型 getXxx() setXxx(Type value)
boolean 类型 isXxx() setXxx(boolean value)
1
2
3
private String name;     // → getName() / setName()
private int age; // → getAge() / setAge()
private boolean active; // → isActive() / setActive() ← 注意是 is 不是 get!

:字段名第二个字母大写时不做变换

private String uRL;getuRL() 而不是 getURL()

所以字段名别搞奇怪的大小写,老老实实用驼峰命名

为什么框架需要 JavaBean

框架通过反射自动操作你的对象,反射需要规则来找方法和字段

Spring 依赖注入

Spring 创建对象时调用无参构造器,然后通过 setter 注入属性值

1
2
3
4
5
6
7
// Spring 配置(简化版)
// 等价于:User user = new User(); user.setName("张三"); user.setAge(25);
@Component
public class UserService {
@Autowired
private User user; // Spring 自动创建并注入
}

MyBatis 数据库映射

查询结果自动映射到 JavaBean:数据库的 user_name 列 → 调用 setUserName()

1
2
3
4
// MyBatis 查询结果自动填充到 User 对象
// SELECT name, age, email FROM users WHERE id = 1
// → new User() → setName("张三") → setAge(25) → setEmail("xxx")
User user = mapper.selectById(1);

JSON 序列化(Jackson / Fastjson)

对象转 JSON 时调 getter,JSON 转对象时调 setter

1
2
3
4
5
6
7
8
User user = new User("张三", 25, "zhangsan@test.com");

// 对象 → JSON:遍历所有 getter 方法
// {"name":"张三","age":25,"email":"zhangsan@test.com"}
String json = objectMapper.writeValueAsString(user);

// JSON → 对象:调无参构造器创建对象,再调 setter 设值
User parsed = objectMapper.readValue(json, User.class);

一句话总结:没有 JavaBean 规范,框架就像没有说明书的拼装玩具,不知道怎么操作你的对象

Lombok 简化 JavaBean(企业开发预告)

每个字段都要写 getter/setter,字段多了之后代码又臭又长,Lombok 帮你自动生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不用 Lombok:一堆模板代码
public class User implements Serializable {
private String name;
private int age;
private String email;

public User() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
// 还有 toString()、equals()、hashCode()... 写到手酸
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 用 Lombok:一个注解搞定
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Data // 自动生成 getter/setter/toString/equals/hashCode
@NoArgsConstructor // 自动生成无参构造器
@AllArgsConstructor // 自动生成全参构造器
public class User implements Serializable {
private String name;
private int age;
private String email;
}
// 完事!上面那一堆全自动生成了

Lombok 是编译时生成代码,不是运行时反射,所以没有性能损耗

企业项目几乎必装 Lombok,IDEA 也需要装 Lombok 插件才能识别

POJO vs JavaBean vs DTO/VO 概念区分

面试和开发中经常混用这几个词,其实它们有区别:

概念 全称 含义 特点
POJO Plain Old Java Object 普通的 Java 对象 没有任何限制,最宽泛的概念
JavaBean - 符合规范的 POJO 必须有无参构造器 + getter/setter
DTO Data Transfer Object 数据传输对象 用于层与层之间传数据,通常是 JavaBean
VO Value Object / View Object 视图对象 用于返回给前端的数据,通常是 JavaBean
Entity - 实体类 对应数据库表,通常是 JavaBean

关系:JavaBean ⊂ POJO,DTO/VO/Entity 通常都是 JavaBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Entity:对应数据库 users 表
@Data
public class UserEntity {
private Long id;
private String username;
private String password; // 包含敏感信息
private String email;
private Date createTime;
}

// DTO:接收前端传来的数据
@Data
public class UserDTO {
private String username;
private String password;
private String email;
}

// VO:返回给前端的数据(脱敏,不返回密码)
@Data
public class UserVO {
private Long id;
private String username;
private String email;
private Date createTime;
// 注意:没有 password 字段!
}

为什么要分这么多类?

安全:Entity 可能包含密码等敏感字段,VO 只返回该暴露的

解耦:数据库表结构变了,只改 Entity,VO 不受影响

清晰:看到类名就知道它是干什么的

完整代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Test
public void testJavaBean() {
// 1. 无参构造器创建 + setter 赋值(框架常用方式)
User user1 = new User();
user1.setName("张三");
user1.setAge(25);
user1.setEmail("zhangsan@test.com");
System.out.println(user1.getName()); // 张三

// 2. 有参构造器一步到位(手动创建时方便)
User user2 = new User("李四", 30, "lisi@test.com");
System.out.println(user2.getAge()); // 30

// 3. setter 可以加校验逻辑(这就是封装的价值)
}

// 加了校验逻辑的 JavaBean
public class ValidatedUser implements Serializable {
private String name;
private int age;

public ValidatedUser() {}

public String getName() { return name; }
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("名字不能为空");
}
this.name = name.trim();
}

public int getAge() { return age; }
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法:" + age);
}
this.age = age;
}
}

常见坑

坑1:写了有参构造器,忘了无参构造器

Java 的规则:如果你不写任何构造器,编译器会自动送你一个无参构造器

但是!一旦你写了有参构造器,自动的无参构造器就消失

框架创建对象时调无参构造器,找不到就报错

1
2
3
4
5
6
7
8
9
10
11
12
public class User {
private String name;

// 写了有参构造器
public User(String name) {
this.name = name;
}

// ❌ 忘了写无参构造器
// MyBatis/Spring 反射创建对象时:boom!
// 报错:No default constructor found
}

解决:永远显式写一个无参构造器,或者用 Lombok 的 @NoArgsConstructor

坑2:boolean 字段的 getter 命名

boolean active → 方法名是 isActive() 不是 getActive()

Boolean active(包装类型大写B)→ 方法名是 getActive()

一不小心就写错,序列化的时候字段名对不上

坑3:字段名以 is 开头

boolean isDeleted → Lombok 生成的 getter 是 isDeleted()(不是 isIsDeleted()

但有些框架会把它识别为字段名 deleted 而不是 isDeleted,导致映射失败

建议:boolean 字段不要以 is 开头,直接用 deletedactive 这种

坑4:Lombok 的 @Data 包含 equals/hashCode

如果类有继承关系,默认生成的 equals 不考虑父类字段,可能导致bug

解决:用 @EqualsAndHashCode(callSuper = true) 让它包含父类

练习题

题1:写一个标准的 JavaBean —— Product 类,字段:name(String)、price(double)、inStock(boolean)

要求:private 字段、getter/setter、无参和有参构造器

注意 boolean 字段的 getter 命名

题2:为什么 MyBatis 查数据库能自动把结果填到 Java 对象里?

答案:MyBatis 通过反射调用无参构造器创建对象,然后根据列名找到对应的 setter 方法调用。这就是为什么必须遵守 JavaBean 规范

题3:Entity、DTO、VO 各自适合在什么层使用?

Entity → 数据库层(Mapper/Repository)

DTO → 服务层(Service),用于接收请求参数

VO → 控制层(Controller),用于返回给前端

三者之间需要做转换,这就是 BeanUtils.copyProperties() 的用武之地


上一章 目录 下一章
内部类与匿名类 java基础 String类与字符串操作