一、get 和 set 方法的基础认知
1.1 定义与作用
get 方法(访问器)和 set 方法(修改器)是面向对象编程中实现封装的关键技术。它们提供了一种标准化的方式来访问和修改类的私有成员变量。在 Java 等面向对象语言中,为了实现良好的封装性,通常会将类的成员变量声明为 private,然后通过 public 的 get 和 set 方法来操作这些变量。
这种设计模式具有以下特点:
get 方法(获取器):用于获取私有成员变量的值,通常以"get"开头set 方法(设置器):用于修改私有成员变量的值,通常以"set"开头方法访问权限:通常设为 public 以保证外部可访问方法命名规范:遵循 JavaBean 规范,采用驼峰命名法
示例:一个完整的 User 类实现
public class User {
// 私有成员变量
private String name;
private int age;
private String email;
// get方法:获取name的值
public String getName() {
return name;
}
// set方法:设置name的值
public void setName(String name) {
this.name = name;
}
// 可以添加更多getter和setter方法
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;
}
}
1.2 为什么需要 get 和 set 方法
虽然直接将成员变量声明为 public 可以简化代码编写,但这种做法会导致严重的设计问题:
数据验证与控制缺失:
无法控制变量的赋值规则例如年龄不能为负数、邮箱必须符合格式等示例(带验证的set方法):
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
this.age = age;
}
内部实现变更的影响:
当变量的内部实现改变时(如从String改为Date)会影响所有直接访问该变量的代码通过方法访问可以保持接口稳定
封装性破坏:
直接暴露成员变量违反面向对象设计原则使得类的内部细节对外可见
额外功能无法添加:
无法在获取或设置时执行额外操作示例(记录访问日志):
public String getName() {
System.out.println("访问了name属性");
return name;
}
线程安全问题:
无法在方法级别添加同步控制示例(线程安全的getter):
public synchronized String getName() {
return name;
}
通过 get 和 set 方法,开发人员可以在不暴露内部实现的前提下,安全地提供变量的访问和修改通道,同时保持对数据访问的完全控制。这种模式是现代面向对象编程的最佳实践之一。
二、get 和 set 方法的命名规范
遵循规范的命名方式是写出可维护、易读代码的基础。在 Java 中,get 和 set 方法作为访问对象属性的标准方式,有着严格的命名约定。这些约定不仅被广泛认可,也是 JavaBean 规范的重要组成部分。
2.1 基本命名规则详解
get 方法命名规则
对于非布尔类型的变量:
命名格式为 get + 首字母大写的变量名例如:getName()、getAge()、getSalary()方法签名示例:public String getName() { return name; }
对于布尔类型的变量:
命名格式通常为 is + 首字母大写的变量名例如:isStudent()、isActive()、isAvailable()方法签名示例:public boolean isStudent() { return student; }
set 方法命名规则
适用于所有变量类型:
命名格式为 set + 首字母大写的变量名例如:setName()、setAge()、setStudent()方法签名示例:public void setName(String name) { this.name = name; }
2.2 命名示例与应用场景
public class Person {
// 成员变量声明
private String name; // 字符串类型
private int age; // 整型
private boolean married; // 布尔类型
// 正确的get/set方法命名示例
// 非布尔类型的getter
public String getName() {
return name;
}
// 非布尔类型的setter
public void setName(String name) {
this.name = name;
}
// 非布尔类型的getter
public int getAge() {
return age;
}
// 非布尔类型的setter
public void setAge(int age) {
this.age = age;
}
// 布尔类型的getter(使用is前缀)
public boolean isMarried() {
return married;
}
// 布尔类型的setter
public void setMarried(boolean married) {
this.married = married;
}
}
实际应用中的注意事项
一致性:在整个项目中应保持命名风格一致特殊情况处理:
对于缩写词(如ID),应保持首字母大写(如getID()而非getId())对于多个单词组成的变量名,每个单词首字母都应大写(如getFirstName())
IDE支持:现代IDE(如IntelliJ IDEA、Eclipse)都提供自动生成getter/setter的功能,可以确保遵循规范
遵循这些命名规范不仅使代码更易读,还能确保与各种框架(如Spring、Hibernate)的良好兼容性。
三、get 和 set 方法的实现细节
3.1 参数与返回值
set 方法
返回值:通常返回 void,表示该方法不返回任何值参数:参数类型必须与对应的成员变量类型完全一致
例如:对于 private String name 成员变量,set 方法应为 public void setName(String name)如果成员变量是自定义对象类型,如 private User user,则 set 方法应为 public void setUser(User user)
get 方法
参数:不接收任何参数返回值:返回值类型必须与对应的成员变量类型完全一致
例如:对于 private int age 成员变量,get 方法应为 public int getAge()如果成员变量是集合类型,如 private List
3.2 方法体中的常见操作
在实际开发中,get 和 set 方法的方法体往往不只是简单的赋值和返回,还可能包含以下高级用法:
3.2.1 参数验证
在 set 方法中可以加入业务逻辑验证,确保数据有效性:
public void setAge(int age) {
// 验证年龄范围
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄必须在0-150之间");
}
// 验证通过后才赋值
this.age = age;
}
其他验证示例:
验证字符串非空:if (name == null || name.trim().isEmpty())验证邮箱格式:使用正则表达式匹配验证数值范围:if (price < 0)
3.2.2 触发事件
当变量值改变时,可以触发相关业务逻辑:
private String address;
public void setAddress(String address) {
// 保存旧值用于比较
String oldAddress = this.address;
this.address = address;
// 只有地址真正改变时才触发通知
if (!Objects.equals(oldAddress, address)) {
notifyAddressChanged();
}
}
private void notifyAddressChanged() {
// 实际业务逻辑可能包括:
// 1. 记录变更日志
// 2. 发送MQ消息通知其他系统
// 3. 更新相关缓存
System.out.println("地址已更新为:" + this.address);
}
典型应用场景:
用户资料变更通知订单状态变更触发业务流程配置参数修改时刷新缓存
3.2.3 延迟加载
在 get 方法中实现资源的按需初始化:
private List
public List
// 第一次访问时才初始化
if (hobbies == null) {
hobbies = new ArrayList<>();
// 可以添加默认值
hobbies.add("阅读");
hobbies.add("运动");
}
return Collections.unmodifiableList(hobbies); // 返回不可修改的视图
}
其他延迟加载场景:
数据库连接的延迟建立大文件的按需加载复杂计算的缓存结果
注意事项:
需要考虑线程安全问题对于频繁访问的属性可能会影响性能要确保延迟初始化的对象不会导致空指针异常
四、get 和 set 方法的使用场景
4.1 JavaBean 规范详解
JavaBean 规范是 Java 平台中用于创建可重用组件的标准规范,其核心要求包括:
类访问权限
类必须声明为 public,确保可以被其他类自由实例化和访问。例如:
public class User {
// 类实现
}
构造方法要求
必须提供一个无参的公共构造方法,这是框架实例化对象的基础。例如:
public User() {} // 默认构造方法
属性访问规范
所有成员变量应该声明为 private,实现封装通过 public 的 getter 和 setter 方法访问属性方法命名遵循规范:getXxx()/setXxx()(布尔类型可用isXxx()) 示例:
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
框架支持优势
遵循 JavaBean 规范的类可以:
被 Spring 等框架自动实例化和依赖注入支持 Hibernate 等 ORM 框架的持久化操作实现对象的深度克隆和序列化方便 IDE 的代码提示和自动补全
4.2 序列化与反序列化机制
JSON 序列化过程
以 Jackson 为例的序列化流程:
通过反射获取所有 public 方法识别 getter 方法(is/get 开头的方法)调用 getter 获取属性值将返回值转换为 JSON 字段
特殊字段处理
即使字段标记为 transient,只要存在 getter 仍会被序列化可使用 @JsonIgnore 注解显式排除 示例:
private transient String password; // 不会被默认序列化
@JsonIgnore
public String getPassword() { // 显式排除
return password;
}
XML 序列化差异
JAXB 等 XML 序列化工具:
依赖 @XmlElement 等注解配置默认也会通过 getter 访问属性需要无参构造方法重建对象
4.3 主流框架集成原理
Spring 框架集成
依赖注入:通过 setter 方法注入依赖对象
@Autowired
public void setService(UserService service) {
this.service = service;
}
配置绑定:@ConfigurationProperties 通过 setter 绑定配置
MyBatis 结果映射
结果集自动映射时调用 setter 方法支持通过 setter 进行类型转换 示例映射:
JPA/Hibernate 特性
延迟加载通过 getter 方法触发查询属性变更检测基于 setter 方法调用支持在 setter 中添加业务逻辑:
public void setAge(int age) {
if(age < 0) throw new IllegalArgumentException();
this.age = age;
}
模板引擎支持
Thymeleaf、JSP EL 表达式等视图技术都依赖 getter 方法访问属性:
五、使用 get 和 set 方法的注意事项
5.1 不要过度使用
在面向对象编程中,并非所有的成员变量都需要提供 get 和 set 方法,过度使用会破坏封装性:
只用于内部计算的临时变量,不需要对外暴露 get/set 方法
例如:缓存计算结果的计算器类中的临时变量这些变量通常被标记为private且不提供任何访问方法
常量(final 修饰的变量)通常只需要 get 方法,不需要 set 方法
例如:数据库连接配置中的MAX_CONNECTION_SIZE常量的值在初始化后就不应被修改
对于不希望被修改的变量,可以只提供 get 方法
例如:用户注册时间createTime这种设计模式被称为"只读属性"
5.2 避免在 get/set 方法中执行耗时操作
get 和 set 方法应该保持简洁高效,遵循最小惊讶原则:
性能影响:
数据库查询或文件IO等操作不应放在get/set中在循环中调用时,如for(int i=0; i<10000; i++) obj.getValue(),性能问题会被放大
可读性问题:
开发者通常认为get/set是简单的属性访问复杂的内部逻辑会使代码难以理解和维护
替代方案:
将复杂操作拆分为独立的方法,如calculateTotal()代替getTotal()
5.3 注意封装性
良好的封装是面向对象设计的重要原则:
避免链式调用破坏封装:
不要像这样设计:public User setName(String name) {this.name=name; return this;}这会导致set操作可以被无限链式调用,违反单一职责原则
集合类型的正确处理:
private List
// 危险做法:外部可以直接修改内部集合
public List
return tags;
}
// 安全做法1:返回防御性副本
public List
return new ArrayList<>(tags);
}
// 安全做法2:返回不可修改视图(Java)
public List
return Collections.unmodifiableList(tags);
}
// 安全做法3:使用不可变集合(Guava)
public ImmutableList
return ImmutableList.copyOf(tags);
}
5.4 注意线程安全
在多线程环境下,get/set方法需要特别考虑:
volatile关键字:
适用于基本类型的原子性操作例如:private volatile int counter;
synchronized关键字:
方法级同步:public synchronized void setValue(int value)块级同步:synchronized(this) { /* 操作 */ }
更高级的并发控制:
使用Lock接口及其实现类使用原子类:AtomicInteger, AtomicReference等对于集合,可以使用ConcurrentHashMap等并发容器
不可变对象:
最安全的做法是设计不可变对象所有字段设为final,不提供setter方法
六、get 和 set 方法的自动化工具
手动编写 get 和 set 方法不仅繁琐,还容易出错,特别是在处理大型类时,需要为每个字段都编写对应的访问方法,既浪费时间又增加了代码维护难度。在实际开发中,我们可以利用以下工具自动生成这些方法,提高开发效率:
6.1 IDE 自带功能
主流 IDE 都提供了快速生成 getter 和 setter 的功能:
Eclipse:在类文件上右键→Source→Generate Getters and Setters,可以选择需要生成方法的字段IntelliJ IDEA:使用快捷键 Alt+Insert→Getters and Setters,支持全选或部分选择字段生成Visual Studio Code:Java 扩展也支持类似功能,可通过命令面板调用
6.2 Lombok 框架
Lombok 是一个 Java 库,通过注解自动生成 getter 和 setter 方法,极大简化代码:
import lombok.Getter;
import lombok.Setter;
// 类级别注解
@Getter @Setter
public class User {
private String name;
private int age;
// 字段级注解
@Getter(AccessLevel.PROTECTED)
private String password;
}
使用 Lombok 需要:
在项目中引入依赖(Maven/Gradle)在 IDE 中安装 Lombok 插件(否则会显示编译错误)启用注解处理(部分 IDE 需要额外配置)
Lombok 还支持生成构造器、toString()、equals()等方法,可以显著减少样板代码。
七、常见问题与解决方案
7.1 命名错误导致框架无法识别
当 getter/setter 方法命名不规范时,许多依赖反射机制的框架(如 Spring、Hibernate、Jackson 等)将无法正确识别和操作对象的属性。
常见问题表现:
框架报错提示找不到属性或方法序列化/反序列化时属性丢失数据绑定失败
解决方法:
严格遵循 JavaBean 命名规范:
getter 方法:getXxx()(布尔属性可以用 isXxx())setter 方法:setXxx(参数类型 value)
使用 IDE 自动生成方法:
在 IntelliJ IDEA 中:右键 → Generate → Getter and Setter在 Eclipse 中:Source → Generate Getters and Setters
使用 Lombok 注解自动生成:
@Getter @Setter
private String name;
7.2 子类覆盖父类的 get/set 方法
当子类需要覆盖父类的 getter/setter 方法时,需要特别注意:
基本规则:
方法签名必须匹配(方法名和参数列表)返回值类型必须兼容(相同或更具体的类型)访问修饰符不能比父类更严格
Java 5+ 支持协变返回类型:
public class Parent {
public Number getValue() {
return 0;
}
}
public class Child extends Parent {
@Override
public Integer getValue() { // 合法,Integer 是 Number 的子类
return 1;
}
}
注意事项:
覆盖 setter 方法时,参数类型必须完全一致避免在覆盖的方法中改变原始语义可以使用 @Override 注解确保正确覆盖
7.3 循环依赖问题
在 getter/setter 方法中调用其他对象的方法时,容易形成循环依赖,导致以下问题:
典型场景:
class A {
private B b;
public B getB() {
if(b == null) {
b = new B();
b.setA(this); // 循环设置
}
return b;
}
}
class B {
private A a;
public A getA() {
if(a == null) {
a = new A();
a.setB(this); // 循环设置
}
return a;
}
}
可能导致的问题:
栈溢出(StackOverflowError)无限递归调用死锁(在多线程环境下)内存泄漏
解决方案:
避免在 getter/setter 中进行复杂的对象初始化使用延迟加载时要特别小心考虑使用设计模式(如工厂模式)管理对象创建对于必须的循环引用,可以:
使用弱引用(WeakReference)在序列化时使用 @JsonIgnore 等注解避免循环采用 DTO 模式断开领域对象的直接关联