Java 中 get 和 set 方法详解(更新版)

 一、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 可以简化代码编写,但这种做法会导致严重的设计问题:

  1. 数据验证与控制缺失

    • 无法控制变量的赋值规则
    • 例如年龄不能为负数、邮箱必须符合格式等
    • 示例(带验证的set方法):
      public void setAge(int age) {
          if (age < 0) {
              throw new IllegalArgumentException("年龄不能为负数");
          }
          this.age = age;
      }
      

  2. 内部实现变更的影响

    • 当变量的内部实现改变时(如从String改为Date)
    • 会影响所有直接访问该变量的代码
    • 通过方法访问可以保持接口稳定
  3. 封装性破坏

    • 直接暴露成员变量违反面向对象设计原则
    • 使得类的内部细节对外可见
  4. 额外功能无法添加

    • 无法在获取或设置时执行额外操作
    • 示例(记录访问日志):
      public String getName() {
          System.out.println("访问了name属性");
          return name;
      }
      

  5. 线程安全问题

    • 无法在方法级别添加同步控制
    • 示例(线程安全的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; 
    }
}

实际应用中的注意事项

  1. 一致性:在整个项目中应保持命名风格一致
  2. 特殊情况处理
    • 对于缩写词(如ID),应保持首字母大写(如getID()而非getId()
    • 对于多个单词组成的变量名,每个单词首字母都应大写(如getFirstName()
  3. 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<String> items,则 get 方法应为 public List<String> getItems()

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<String> hobbies;

public List<String> getHobbies() {
    // 第一次访问时才初始化
    if (hobbies == null) {
        hobbies = new ArrayList<>();
        // 可以添加默认值
        hobbies.add("阅读");
        hobbies.add("运动");
    }
    return Collections.unmodifiableList(hobbies); // 返回不可修改的视图
}

其他延迟加载场景:

  • 数据库连接的延迟建立
  • 大文件的按需加载
  • 复杂计算的缓存结果

注意事项:

  • 需要考虑线程安全问题
  • 对于频繁访问的属性可能会影响性能
  • 要确保延迟初始化的对象不会导致空指针异常

四、get 和 set 方法的使用场景

4.1 JavaBean 规范详解

JavaBean 规范是 Java 平台中用于创建可重用组件的标准规范,其核心要求包括:

  1. 类访问权限
    类必须声明为 public,确保可以被其他类自由实例化和访问。例如:

    public class User {
        // 类实现
    }
    

  2. 构造方法要求
    必须提供一个无参的公共构造方法,这是框架实例化对象的基础。例如:

    public User() {} // 默认构造方法
    

  3. 属性访问规范

    • 所有成员变量应该声明为 private,实现封装
    • 通过 public 的 getter 和 setter 方法访问属性
    • 方法命名遵循规范:getXxx()/setXxx()(布尔类型可用isXxx()) 示例:
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    

  4. 框架支持优势
    遵循 JavaBean 规范的类可以:

    • 被 Spring 等框架自动实例化和依赖注入
    • 支持 Hibernate 等 ORM 框架的持久化操作
    • 实现对象的深度克隆和序列化
    • 方便 IDE 的代码提示和自动补全

4.2 序列化与反序列化机制

  1. JSON 序列化过程
    以 Jackson 为例的序列化流程:

    • 通过反射获取所有 public 方法
    • 识别 getter 方法(is/get 开头的方法)
    • 调用 getter 获取属性值
    • 将返回值转换为 JSON 字段
  2. 特殊字段处理

    • 即使字段标记为 transient,只要存在 getter 仍会被序列化
    • 可使用 @JsonIgnore 注解显式排除 示例:
    private transient String password; // 不会被默认序列化
    
    @JsonIgnore
    public String getPassword() { // 显式排除
        return password;
    }
    

  3. XML 序列化差异
    JAXB 等 XML 序列化工具:

    • 依赖 @XmlElement 等注解配置
    • 默认也会通过 getter 访问属性
    • 需要无参构造方法重建对象

4.3 主流框架集成原理

  1. Spring 框架集成

    • 依赖注入:通过 setter 方法注入依赖对象
      @Autowired
      public void setService(UserService service) {
          this.service = service;
      }
      

    • 配置绑定:@ConfigurationProperties 通过 setter 绑定配置
  2. MyBatis 结果映射

    • 结果集自动映射时调用 setter 方法
    • 支持通过 setter 进行类型转换 示例映射:
    <resultMap id="userMap" type="User">
        <result property="name" column="user_name"/>
    </resultMap>
    

  3. JPA/Hibernate 特性

    • 延迟加载通过 getter 方法触发查询
    • 属性变更检测基于 setter 方法调用
    • 支持在 setter 中添加业务逻辑:
      public void setAge(int age) {
          if(age < 0) throw new IllegalArgumentException();
          this.age = age;
      }
      

  4. 模板引擎支持
    Thymeleaf、JSP EL 表达式等视图技术都依赖 getter 方法访问属性:

    <div th:text="${user.name}">...</div>
    

五、使用 get 和 set 方法的注意事项

5.1 不要过度使用

在面向对象编程中,并非所有的成员变量都需要提供 get 和 set 方法,过度使用会破坏封装性:

  1. 只用于内部计算的临时变量,不需要对外暴露 get/set 方法

    • 例如:缓存计算结果的计算器类中的临时变量
    • 这些变量通常被标记为private且不提供任何访问方法
  2. 常量(final 修饰的变量)通常只需要 get 方法,不需要 set 方法

    • 例如:数据库连接配置中的MAX_CONNECTION_SIZE
    • 常量的值在初始化后就不应被修改
  3. 对于不希望被修改的变量,可以只提供 get 方法

    • 例如:用户注册时间createTime
    • 这种设计模式被称为"只读属性"

5.2 避免在 get/set 方法中执行耗时操作

get 和 set 方法应该保持简洁高效,遵循最小惊讶原则:

  1. 性能影响:

    • 数据库查询或文件IO等操作不应放在get/set中
    • 在循环中调用时,如for(int i=0; i<10000; i++) obj.getValue(),性能问题会被放大
  2. 可读性问题:

    • 开发者通常认为get/set是简单的属性访问
    • 复杂的内部逻辑会使代码难以理解和维护
  3. 替代方案:

    • 将复杂操作拆分为独立的方法,如calculateTotal()代替getTotal()

5.3 注意封装性

良好的封装是面向对象设计的重要原则:

  1. 避免链式调用破坏封装:

    • 不要像这样设计:public User setName(String name) {this.name=name; return this;}
    • 这会导致set操作可以被无限链式调用,违反单一职责原则
  2. 集合类型的正确处理:

    private List<String> tags = new ArrayList<>();
    
    // 危险做法:外部可以直接修改内部集合
    public List<String> getTags() {
        return tags; 
    }
    
    // 安全做法1:返回防御性副本
    public List<String> getTags() {
        return new ArrayList<>(tags);
    }
    
    // 安全做法2:返回不可修改视图(Java)
    public List<String> getTags() {
        return Collections.unmodifiableList(tags);
    }
    
    // 安全做法3:使用不可变集合(Guava)
    public ImmutableList<String> getTags() {
        return ImmutableList.copyOf(tags);
    }
    

5.4 注意线程安全

在多线程环境下,get/set方法需要特别考虑:

  1. volatile关键字:

    • 适用于基本类型的原子性操作
    • 例如:private volatile int counter;
  2. synchronized关键字:

    • 方法级同步:public synchronized void setValue(int value)
    • 块级同步:synchronized(this) { /* 操作 */ }
  3. 更高级的并发控制:

    • 使用Lock接口及其实现类
    • 使用原子类:AtomicInteger, AtomicReference等
    • 对于集合,可以使用ConcurrentHashMap等并发容器
  4. 不可变对象:

    • 最安全的做法是设计不可变对象
    • 所有字段设为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 需要:

    1. 在项目中引入依赖(Maven/Gradle)
    2. 在 IDE 中安装 Lombok 插件(否则会显示编译错误)
    3. 启用注解处理(部分 IDE 需要额外配置)

    Lombok 还支持生成构造器、toString()、equals()等方法,可以显著减少样板代码。

    七、常见问题与解决方案

    7.1 命名错误导致框架无法识别

    当 getter/setter 方法命名不规范时,许多依赖反射机制的框架(如 Spring、Hibernate、Jackson 等)将无法正确识别和操作对象的属性。

    常见问题表现:

    • 框架报错提示找不到属性或方法
    • 序列化/反序列化时属性丢失
    • 数据绑定失败

    解决方法:

    1. 严格遵循 JavaBean 命名规范:
      • getter 方法:getXxx()(布尔属性可以用 isXxx()
      • setter 方法:setXxx(参数类型 value)
    2. 使用 IDE 自动生成方法:
      • 在 IntelliJ IDEA 中:右键 → Generate → Getter and Setter
      • 在 Eclipse 中:Source → Generate Getters and Setters
    3. 使用 Lombok 注解自动生成:
      @Getter @Setter
      private String name;
      

    7.2 子类覆盖父类的 get/set 方法

    当子类需要覆盖父类的 getter/setter 方法时,需要特别注意:

    1. 基本规则:

      • 方法签名必须匹配(方法名和参数列表)
      • 返回值类型必须兼容(相同或更具体的类型)
      • 访问修饰符不能比父类更严格
    2. Java 5+ 支持协变返回类型:

      public class Parent {
          public Number getValue() {
              return 0;
          }
      }
      
      public class Child extends Parent {
          @Override
          public Integer getValue() { // 合法,Integer 是 Number 的子类
              return 1;
          }
      }
      

    3. 注意事项:

      • 覆盖 setter 方法时,参数类型必须完全一致
      • 避免在覆盖的方法中改变原始语义
      • 可以使用 @Override 注解确保正确覆盖

    7.3 循环依赖问题

    在 getter/setter 方法中调用其他对象的方法时,容易形成循环依赖,导致以下问题:

    1. 典型场景:

      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;
          }
      }
      

    2. 可能导致的问题:

      • 栈溢出(StackOverflowError)
      • 无限递归调用
      • 死锁(在多线程环境下)
      • 内存泄漏
    3. 解决方案:

      • 避免在 getter/setter 中进行复杂的对象初始化
      • 使用延迟加载时要特别小心
      • 考虑使用设计模式(如工厂模式)管理对象创建
      • 对于必须的循环引用,可以:
        • 使用弱引用(WeakReference)
        • 在序列化时使用 @JsonIgnore 等注解避免循环
        • 采用 DTO 模式断开领域对象的直接关联
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值