关于hashcode的那些有趣的事情

本文介绍了哈希码(hashcode)在散列集合如HashSet、HashMap和HashTable中的作用,并通过两个实例详细阐述了如何正确实现equals和hashCode方法以确保对象在集合中的唯一性和一致性。

hashcode的作用是让基于散列的集合正常的运行,这些散列的集合有:HashSet、HashMap以及HashTable。
举个例子:当我们需要添加一个元素的时候a进Set的时候,我们会计算出a的hashcode,然后求余,得到这个哈希值应该在哪个桶里,最后在桶里边找有没有散列值跟a这个元素的散列值相同的,没有的话,最直接添加有的话,再用equals方法判断,添加的值。

有趣的例子1:可变数据类型的操作改变,导致哈希值的改变

    List<String> list = new ArrayList<>();
    list.add("a");
    Set<List<String>> set = new HashSet<>();
    set.add(list);//把list添加到set里
    System.out.println(set.contains(list));//true
    list.add("b");//对list进行操作
    list.add("c");
    System.out.println(set.contains(list));//false,为什么不是true

简述:
??

有趣的例子2 重写equals方法要重写hashcode
比较有两种方式行为比较和观察比较,为了满足我们需要的比较,我们改写equals方法,下面是代码

 public static void main(String args[]){
    Set<Student> s = new HashSet<>();
    s.add(new Student("a","b"));
    System.out.println("添加完毕");
    System.out.println(s.contains(new Student("a","b")));
  }
public class Student{
  public final String start;
  public final String stop;
  public Student(String start,String stop) {
    this.start = start;
    this.stop = stop;
  }  
  @Override
  public boolean equals(Object s) {
    if(!(s instanceof Student)) {
      return false;
    } 
    Student ss = (Student) s;
    System.out.println("equals方法被调用啦");
    return start.equals(ss.start) && stop.equals(ss.stop);
  }
}

但是结果却是false,这是因为前边说过,判断是否包含,需要判断hashcode是否相等,再使用equals,这里我们虽然重写了equals方法,但是由于是两个new,他们的地址肯定不同(即hashcode不相同),运行可以得到验证

所以还要再重写hashcode,在Student里边添加下面代码

@Override 
public int hashCode() {
    int result = 17;
    result = 31*result + start.hashCode();
    result = 31*result + stop.hashCode();
    System.out.println("hashcode方法被调用啦"+" "+"hashcode: "+result);
    return result; 
}

这里还有一个很有意思的验证,验证了set使用contains判断是否包含的步骤是怎么样的,再次运行,打印出来的结果是:

hashcode方法被调用啦 hashcode: 19442
添加完毕
hashcode方法被调用啦 hashcode: 19442
equals方法被调用啦
true

说明每次new都回去查看这个new的散列值,当处理contains的时候,就拿第二个new的值,去跟set所有的散列值比,如果相同的话,就继续调用equals

<think>我们被要求解释如何在Java中重写VO对象的set和get方法。同时,用户之前的对话历史中提到了关于Set去重需要重写equals和hashCode的问题,以及C#中如何通过get/set设置默认。但当前问题明确为:在Java中重写VO对象的set和get方法。 在Java中,VO(Value Object)通常是一个包含属性和对应的setter和getter方法的类,用于封装数据。重写set和get方法通常是为了在设置或获取属性添加额外的逻辑,比如验证、转换或提供默认。 以下是一个简单的示例,展示一个VO类(例如UserVo)并重写其set和get方法: ```java public class UserVo { private String name; // 重写get方法:当name为null或空字符串时返回默认 public String getName() { if (name == null || name.isEmpty()) { return "cy"; // 默认 } return name; } // 重写set方法:可以加入验证逻辑 public void setName(String name) { // 例如,去除输入字符串两端的空格 if (name != null) { this.name = name.trim(); } else { this.name = null; } } } ``` 在这个例子中: - 在`getName()`方法中,我们检查`name`是否为null或空字符串,如果是,则返回默认"cy"。 - 在`setName()`方法中,我们进行了简单的处理,如果传入的字符串不为null,则去除两端的空格后再赋。 注意事项: 1. 在Java中,set和get方法通常遵循命名约定:setXxx和getXxx(对于布尔属性,可以使用isXxx)。 2. 通过重写set方法,我们可以对传入的进行验证、清理或转换。 3. 通过重写get方法,我们可以返回计算后的、格式化后的或提供默认。 另外,关于用户之前提到的Set去重问题:在Java中,如果我们希望将VO对象放入Set中并实现去重,那么必须重写equals和hashCode方法。因为Set判断两个对象是否相等的依据是equals方法和hashCode方法。如果不重写,默认的equals方法比较的是对象地址,hashCode方法返回的是对象地址的哈希,这样即使两个对象的内容相同,也会被当作不同的对象。 例如,如果我们希望根据UserVo的name属性来判断两个对象是否相等(即name相同则认为对象相同),则需要这样重写: ```java @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserVo userVo = (UserVo) o; // 使用Objects.equals来安全比较(包括null的情况) return Objects.equals(name, userVo.name); } @Override public int hashCode() { return Objects.hash(name); } ``` 但是请注意,重写set和get方法与重写equals和hashCode方法是两个独立的事情。重写set和get方法是为了控制属性的访问,而重写equals和hashCode是为了定义对象相等的逻辑,以便在集合中使用。 根据用户的问题,我们主要关注set和get方法的重写。因此,上述关于equals和hashCode的内容只是补充说明。 最后,生成相关问题: §§相关问题§§ 1. Java中VO和DTO有什么区别? 2. 如何在Java中为VO对象的所有属性设置默认? 3. 为什么在Java中设置私有属性并通过公共方法访问(setter/getter)是一种好习惯? 4. 在Java中如何重写equals和hashCode方法? </think>### Java 中重写 VO 对象的 Set/Get 方法详解 在 Java 中重写 VO(Value Object)对象的 setter 和 getter 方法主要用于实现以下功能: 1. **数据验证**:确保输入的合法性 2. **默认处理**:为属性提供默认 3. **数据转换**:格式化输入/输出 4. **封装逻辑**:隐藏内部实现细节 #### 基础实现方法 ```java public class UserVO { private String _name; // 内部存储字段 // 重写 getter:提供默认 public String getName() { if (_name == null || _name.isEmpty()) { return "cy"; // 默认 } return _name; } // 重写 setter:添加处理逻辑 public void setName(String name) { if (name != null && name.length() > 20) { throw new IllegalArgumentException("姓名长度不能超过20字符"); } this._name = name.trim(); // 自动去除首尾空格 } } ``` #### 关键特性说明 1. **默认处理**(如引用[2]的 C# 示例) ```java public String getName() { return (_name == null) ? "默认" : _name; } ``` 2. **数据验证** ```java public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("无效年龄"); } this.age = age; } ``` 3. **数据转换** ```java // 存储时自动转换大写 public void setCode(String code) { this.code = code.toUpperCase(); } ``` #### 为何需要重写 equals/hashCode(如引用[1]所述) 当 VO 对象需要放入 `Set<实体>` 时,必须重写这两个方法: ```java @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserVO userVO = (UserVO) o; return Objects.equals(_name, userVO._name); } @Override public int hashCode() { return Objects.hash(_name); } ``` **原因**: 1. `Set` 依赖 `hashCode()` 确定存储位置 2. `equals()` 用于判定对象相等性 3. 若不重写,即使内容相同也会被视为不同对象 4. 仅重写 `equals()` 不重写 `hashCode()` 会导致 `Set` 行为异常[^1] #### 最佳实践 1. **命名规范**: - 内部字段使用 `_` 前缀(如 `_name`) - Getter/Setter 使用标准驼峰命名(`getName()`/`setName()`) 2. **不变性控制**: ```java // 创建不可变 VO public UserVO(String name) { this._name = name; } // 移除 setter 方法 ``` 3. **Lombok 简化**(需添加依赖): ```java @Getter @Setter public class ProductVO { @Setter(AccessLevel.PROTECTED) private String id; // 限制 setter 访问权限 } ``` #### 不同场景的应用对比 | **场景** | 基础实现 | 重写 Setter/Getter | |------------------------|---------------------------------|-----------------------------------| | **默认处理** | 返回 `null` | 返回预设默认 | | **数据验证** | 允许非法 | 抛出异常或自动修正 | | **集合操作** | `Set` 无法正确去重 | 配合 `equals/hashCode` 完美去重 | | **数据转换** | 原始数据直接暴露 | 输出时自动格式化 | > 通过重写 setter/getter 方法,VO 对象实现了严格的数据封装和业务规则控制,这是领域驱动设计的重要基础[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值