前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JAVA 拾遗--eqauls 和 hashCode 方法

JAVA 拾遗--eqauls 和 hashCode 方法

作者头像
kirito-moe
发布于 2018-04-27 03:21:10
发布于 2018-04-27 03:21:10
1.1K00
代码可运行
举报
运行总次数:0
代码可运行

缘起—lombok 引发的惨案

Lombok 是一种 Java™ 实用工具,可用于帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。

最近一个新项目中开始使用了 lombok,由于其真的是太简单易懂了,以至于我连文档都没看,直接就上手使用了,引发了一桩惨案。

实体类定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1@Data
2public class Project {
3    private Long id;
4    private String projectName;
5    private List<Project> projects;
6}

我在项目中设计了一个 Project 类,其包含了一个 List projects 属性,表达了项目间的依赖关系。@Data 便是 Lombok 提供的常用注解,我的本意是使用它来自动生成 getter/setter 方法。这样的实体类定义再简单不过了。

意外出现

使用 Project 类表达项目间的依赖关系是我的初衷,具体的分析步骤不在此赘述,对 Project 类的操作主要包括创建,打印,保存几个简单操作。运行初期,一切看似风平浪静,但经过长时间运行后,我意外的获得了如下的异常:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1Exception in thread "Tmoe.cnkirito.dependency0" java.lang.StackOverflowError
2 at moe.cnkirito.dependency.model.Project.hashCode(Project.java:20)
3 at java.util.AbstracList.hashCode(AbstractList.java:541)

这让我感到很意外,我并没有对 Project 类进行什么复杂的操作,也没有进行什么递归操作,怎么会得到 StackOverflowError 这个错误呢?更令我百思不得其解的地方在于,怎么报错的日志中还出现了 hashCode 和 AbstractList 这两个家伙?等等…hashCode…emmmmm…我压根没有重写过它啊,怎么可能会报错呢….再想了想 Lombok 的 @Data 注解,我似乎发现了什么…emmmmm…抱着怀疑的态度翻阅了下 Lombok 的文档,看到了如下的介绍

@Data is a convenient shortcut annotation that bundles the features of @ToString, @EqualsAndHashCode, @Getter / @Setter and @RequiredArgsConstructor together: In other words, @Data generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toString, equals and hashCode implementations that involve the fields of the class, and a constructor that initializes all final fields, as well as all non-final fields with no initializer that have been marked with @NonNull, in order to ensure the field is never null.

原来 @Data 注解不仅帮我们实现了生成了@Getter / @Setter 注解,还包含了@ToString, @EqualsAndHashCode, 和 @RequiredArgsConstructor 注解,这其中的 @EqualsAndHashCode 注解似乎和我这次的惨案密切相关了。顺藤摸瓜,看看 @EqualsAndHashCode 的文档:

Any class definition may be annotated with @EqualsAndHashCode to let lombok generate implementations of the equals(Object other) and hashCode() methods. By default, it'll use all non-static, non-transient fields

@EqualsAndHashCode 会自动生成 equals(Object other)hashCode() 两个方法,默认会使用所有非静态,非瞬时状态的字段。

回到我的案例中,也就是说,Lombok 会将 Project 类中的 List projects 当做是 hashCode 计算的一部分(同理,equals,toString 也会存在同样的问题),而如果我的项目中出现循环引用,这就会导致死循环,最终就会抛出 StackOverFlowError。

为了验证我的想法,简化的项目中的代码后,来测试下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1public String testHashCode(){
2    Project project = new Project();
3    Project other = new Project();
4    other.setProjects(Arrays.asList(project));
5    project.setProjects(Arrays.asList(other));
6    System.out.println(project.hashCode());
7    return "success";
8}

调用该代码后,复现了上述的异常。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1Exception in thread "Tmoe.cnkirito.dependency0" java.lang.StackOverflowError
2 at moe.cnkirito.dependency.model.Project.hashCode(Project.java:20)
3 at java.util.AbstracList.hashCode(AbstractList.java:541)

紧接着,继续测试下 toString 和 eqauls 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1## 测试循环引用实体类中下的 toString 方法
 2java.lang.StackOverflowError: null
 3    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:125) ~[na:1.8.0_161]
 4    at java.lang.AbstractStringBuilder.appendNull(AbstractStringBuilder.java:493) ~[na:1.8.0_161]
 5    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:446) ~[na:1.8.0_161]
 6    at java.lang.StringBuilder.append(StringBuilder.java:136) ~[na:1.8.0_161]
 7    at com.qianmi.dependency.model.Project.toString(Project.java:18) ~[classes/:na]
 8## 测试循环引用实体类中下的 equals 方法    
 9java.lang.StackOverflowError: null
10    at java.util.AbstractList.rangeCheckForAdd(AbstractList.java:604) ~[na:1.8.0_161]
11    at java.util.AbstractList.listIterator(AbstractList.java:325) ~[na:1.8.0_161]
12    at java.util.AbstractList.listIterator(AbstractList.java:299) ~[na:1.8.0_161]
13    at java.util.AbstractList.equals(AbstractList.java:518) ~[na:1.8.0_161]
14    at com.qianmi.dependency.model.Project.equals(Project.java:18) ~[classes/:na]

不出所料,都存在同样的问题。

这一案例可以稍微总结下,一是在使用新的技术框架(Lombok)之前没有看文档,对其特性不太了解,望文生义,认为 @Data 不会重写 hashCode 等方法,二是没有考虑到 hashCode,eqauls 等方法应该如何正确地覆盖。

回顾 JAVA 中最基础的方法: hashCode 和 equals

这两个方法说是 JAVA 最基础的方法一点不为过,但往往越基础的东西越容易被人忽视,让我想起了 JAVA 闲聊群中一位长者经常吐槽的一点:『现在的面试、群聊动不动就是高并发,JVM,中间件,却把基础给遗忘了』。 我感觉很幸运,在当初刚学 JAVA 时,便接触了一本神书《effective java》,一本号称怎么夸都不为过的书,它的序是这么写的

我很希望10年前就拥有这本书。可能有人认为我不需要任何Java方面的书籍,但是我需要这本书。 ——Java 之父 James Gosling

其书中的第三章第 8 条,第 9 条阐述了 equals 和 hashCode 的一些重写原则,我将一些理论言简意赅的阐述在本节中,喜欢的话推荐去看原书哦。

第8条:覆盖equals时请遵守通用约定

什么时候应该覆盖Object.equals呢?如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。这通常属于“值类(value class)”的情形。值类仅仅是一个表示值的类,例如Integer或者Date。程序员在利用equals方法来比较值对象的引用时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象。为了满足程序员的要求,不仅必需覆盖equals方法,而且这样做也使得这个类的实例可以被用作映射表(map)的键(key),或者集合(set)的元素,使映射或者集合表现出预期的行为。 在覆盖equals方法的时候,你必须要遵守它的通用约定。下面是约定的内容,来自Object的规范[JavaSE6]: equals方法实现了等价关系(equivalence relation)

  • 自反性(reflexive)。对于任何非null的引用值xx.equals(x)必须返回true
  • 对称性(symmetric)。对于任何非null的引用值xy,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
  • 传递性(transitive)。对于任何非null的引用值xyz。如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true
  • 一致性(consistent)。对于任何非null的引用值xy,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(x)就会一致地返回true,或者一致的返回false
  • 对于任何非null的引用值xx.equals(null)必须返回false

学过高数,离散的同学不会对上述的理论陌生,它们源自于数学理论,没了解过这些概念的同学也不必有所顾忌,因为你只需要养成习惯,在设计一个实体类时时刻惦记着上述几个关系,能符合的话大概就没有问题。结合所有这些要求,得出了以下实现高质量equals方法的诀窍:

  1. 使用==操作符检查“参数是否为这个对象的引用”。如果是,则返回true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。
  2. 使用 instanceof 操作符检查“参数是否为正确的类型”。如果不是,则返回false。一般说来,所谓“正确的类型”是指equals方法所在的那个类。有些情况下,是指该类所实现的某个接口。如果类实现的接口改进了equals约定,允许在实现了该接口的类之间进行比较,那么就使用接口。集合接口(collection interface)如SetListMapMap.Entry具有这样的特性。
  3. 把参数转换成正确的类型。因为转换之前进行过instanceof测试,所以确保会成功。
  4. 对于该类中每个“关键(significant)域,检查参数中的域是否与该对象中对应的域相匹配”。如果这些测试全部成功,则返回true;否则返回false。如果第2步中的类型是个借口,就必须通过接口方法访问参数中的域;如果该类型是个类,也许就能够直接访问参数中的域,这要取决于它们的可访问性。 对于既不是float也不是double类型的基本类型域,可以使用==操作符进行比较;对于对象引用域,可以递归地调用equals方法;对于float域,可以使用Float.compare方法;对于double域,则使用Double.compare。对于floatdouble域进行特殊的处理是有必要的,因为存在着Float.NaN-0.0f以及类似的double常量;详细信息请参考Float.equals的文档。对于数组域,则要把以上这些指导原则应用到每个元素上。如果数组域中的每个元素都很重要,就可以使用发行版本1.5中新增的其中一个Arrays.equals方法。 有些对象引用域包含null可能是合法的,所以,为了避免可能导致NullPointerException异常,则使用下面的习惯用法来比较这样的域: 1(field == null ? o.field == null : field.equals(o.field)) 如果field域和o.field通常是相同的对象引用,那么下面的做法就会更快一些: 1(field == o.field || (field != null && field.equals(o.field)))
  5. 当你编写完成了equals方法之后,应该问自己三个问题:它是不是对称的、传递的、一致的?并且不要只是自问,还要编写单元测试来检验这些特性!如果答案是否定的,就要找出原因,再相应地修改equals方法的代码。当然,equals方法也必须满足其他两个特性(自反性和非空性),但是这两种特性通常会自动满足。

其他原则还包括:

  • 覆盖 equals 时总要覆盖 hashCode。(在下一节中介绍)
  • 不要企图让 equals 方法过于智能。如果只是简单地测试域中的值是否相等,则不难做到遵守equals约定。如果想过度地去寻求各种等价关系,则很容易陷入麻烦之中。把任何一种别名形式考虑到等价的范围内,往往不会是个好主意。例如,File类不应该视图把指向同一个文件的符号链接(symbolic link)当作相等的对象来看待。所幸File类没有这样做。
  • 不要将 equals 声明中的 Object 对象替换为其他的类型
第9条:覆盖equals时总要覆盖hashCode

一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashcode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,这样的集合包括HashMapHashSetHashtable。 下面是约定的内容,摘自Object规范[JavaSE6]:

  • 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
  • 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
  • 如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。

因没有覆盖hashCode而违反的关键约定是第二条:相等的对象必须具有相等的散列码(hash code)。根据类的equals方法,两个截然不同的实例在逻辑上有可能是相等的,但是,根据Object类的hashCode方法,它们仅仅是两个没有任何共同之处的对象。因此,对象的hashCode方法返回两个看起来是随机的整数,而不是根据第二个约定所要求的那样,返回两个相等的整数。

默默看完书中的文字,是不是觉得有点哲学的韵味呢,写好一手代码真的不容易。

实战中如何重写 hashCode 和 equals?

hashCode 和 equals 很重要,在使用中,与之密切相关的一般是几个容器类:HashMap 和 HashSet,意味着当我们将一个类作为其中的元素时,尤其需要考量下 hashCode 和 equals 的写法。

话不多数,即刻介绍。对了,你指望我手敲 hashCode 和 equals 吗?不存在的,程序员应该优雅的偷懒,无论你是 eclipse 玩家还是 idea 玩家,都能找到对应的快捷键,帮你自动重写这两个方式,我们要做的就是对参数的选择做一些微调。例如使用 idea 生成下面这个类的 hashCode 和 equals 方法,设置前提:将所有字段当做关键(significant)域。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1public class Example {
2    private int a;
3    private float b;
4    private double c;
5    private BigDecimal d;
6    private char e;
7    private byte f;
8    private String g;
9}

方法一:Intellij Default

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1@Override
 2public boolean equals(Object o) {
 3    if (this == o) return true;
 4    if (o == null || getClass() != o.getClass()) return false;
 5    if (!super.equals(o)) return false;
 6
 7    Example example = (Example) o;
 8
 9    if (a != example.a) return false;
10    if (Float.compare(example.b, b) != 0) return false;
11    if (Double.compare(example.c, c) != 0) return false;
12    if (e != example.e) return false;
13    if (f != example.f) return false;
14    if (!d.equals(example.d)) return false;
15    return g.equals(example.g);
16}
17
18@Override
19public int hashCode() {
20    int result = super.hashCode();
21    long temp;
22    result = 31 * result + a;
23    result = 31 * result + (b != +0.0f ? Float.floatToIntBits(b) : 0);
24    temp = Double.doubleToLongBits(c);
25    result = 31 * result + (int) (temp ^ (temp >>> 32));
26    result = 31 * result + d.hashCode();
27    result = 31 * result + (int) e;
28    result = 31 * result + (int) f;
29    result = 31 * result + g.hashCode();
30    return result;
31}

这可能是大家最熟悉的方法,先来分析下 equals 的写法。看样子的确是遵循了《effective java》中提及的 java1.6 规范的,值得注意的点再强调下:Float 和 Double 类型的比较应该使用各自的静态方法 Float.compare 和 Double.compare。

hashCode 方法则更加有趣一点,你可能会有如下的疑问:

  • Double.doubleToLongBits 是干嘛用的?
  • 为啥是 31?
  • 为什么还有 ^,>>> 这些运算符号?

带着疑问来看看下面的解释。

一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。这正是上一节中hashCode约定中第三条的含义。理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。要想完全达到这种理想的情形是非常困难的。但相对接近这种理想情形则并不太苦难。《effective java》给出了一种简单的解决办法:

  1. 把某个非零的常数值,比如说17,保存在一个名为resultint类型的变量中。
  2. 对于对象中每个关键域f(指equals方法中涉及的每个域),完成以下步骤: a. 为该域计算int类型的散列码c: i. 如果该域是boolean类型,则计算(f ? 1 : 0). ii. 如果该域是bytecharshort或者int类型,则计算(int)f。 iii. 如果该域是long类型,则计算(int)(f ^ (f >>> 32))。 iv. 如果该域是float类型,则计算Float.floatToIntBits(f)。 v. 如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤2.a.iii,为得到的long类型值计算散列值。 vi. 如果该域是一个对象引用,并且该域的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式(canonical representation)”,然后针对这个范式调用hashCode。如果这个域的值为null,则返回0(或者其他某个常数,但通常是0)。 vii. 如果该域是一个数组,则要把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法。 b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中: 1result = 31 * result + c;
  3. 返回result。
  4. 写完了hashCode方法之后,问问自己“相等的实例是否都具有相等的散列码”。要编写单元测试来验证你的推断。如果相等实例有着不相等的散列码,则要找出原因,并修正错误。

在散列码的计算过程中,可以把冗余域(redundant field)排除在外。换句话说,如果一个域的值可以根据参与计算的其他域值计算出来,则可以把这样的域排除在外。必须排除equals比较计算中没有用到的任何域,否则很有可能违反hashCode约定的第二条。

上述步骤1中用到了一个非零的初始值,因此步骤2.a中计算的散列值为0的那些初始域,会影响到散列值。如果步骤1中的初始值为0,则整个散列值将不受这些初始域的影响,因为这些初始域会增加冲突的可能性。值17则是任选的。

步骤2.b中的乘法部分使得散列值依赖于域的顺序,如果一个类包含多个相似的域,这样的乘法运算就会产生一个更好的散列函数。例如,如果String散列函数省略了这个乘法部分,那么只是字母顺序不同的所有字符串都会有相同的散列码。之所以选择31,是因为它是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于位移运算。使用素数的好处并不很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,即用位移和减法来代替乘法,可以得到更好的性能,31 * i == (i << 5) - i。现代的VM可以自动完成这种优化。

是不是几个疑惑都解开了呢?

方法二:Objects.hash 和 Objects.equals

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1@Override
 2public boolean equals(Object o) {
 3    if (this == o) return true;
 4    if (o == null || getClass() != o.getClass()) return false;
 5    if (!super.equals(o)) return false;
 6    Example example = (Example) o;
 7    return a == example.a &&
 8            Float.compare(example.b, b) == 0 &&
 9            Double.compare(example.c, c) == 0 &&
10            e == example.e &&
11            f == example.f &&
12            Objects.equals(d, example.d) &&
13            Objects.equals(g, example.g);
14}
15
16@Override
17public int hashCode() {
18    return Objects.hash(super.hashCode(), a, b, c, d, e, f, g);
19}

JAVA 是一个与时俱进的语言,有问题从自身解决,便利了开发者,如《effective java》所言,在 jdk1.6 中上述那些原则只是一纸空文。错误同真理的关系,就象睡梦同清醒的关系一样。一个人从错误中醒来,就会以新的力量走向真理。在 jdk1.7 中便造就了诸多的方法 Objects.hash 和 Objects.equals 帮助你智能的实现 hashCode 和 equals 方法。很明显,代码量上比方法一少了很多,并且有了 jdk 的原生支持,心里也更加有底了。

方法三:Lombok 的 @EqualsAndHashCode

前面已经提到了 Lombok 的这个注解,在此详细介绍下这个注解的用法,方便大家写出规范的 hashCode 和 equals 方法。

  1. 此注解会生成equals(Object other)hashCode()方法。
  2. 它默认使用非静态,非瞬态的属性
  3. 可通过参数exclude排除一些属性
  4. 可通过参数of指定仅使用哪些属性
  5. 它默认仅使用该类中定义的属性且不调用父类的方法
  6. 可通过callSuper=true解决上一点问题。让其生成的方法中调用父类的方法。

使用 Lombok 很便捷,整个代码也很清爽

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1@Data
 2@EqualsAndHashCode(of = {"a","b","c","d","e","f","g"})//默认就是所有参数
 3public class Example {
 4
 5    private int a;
 6    private float b;
 7    private double c;
 8    private BigDecimal d;
 9    private char e;
10    private byte f;
11    private String g;
12
13}

如果想知道编译过后的庐山真面目,也可以在 target 包中找到 Example.java 生成的 Example.class,:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1public boolean equals(Object o) {
 2    if (o == this) {
 3        return true;
 4    } else if (!(o instanceof Example)) {
 5        return false;
 6    } else {
 7        Example other = (Example)o;
 8        if (!other.canEqual(this)) {
 9            return false;
10        } else if (this.getA() != other.getA()) {
11            return false;
12        } else if (Float.compare(this.getB(), other.getB()) != 0) {
13            return false;
14        } else if (Double.compare(this.getC(), other.getC()) != 0) {
15            return false;
16        } else {
17            Object this$d = this.getD();
18            Object other$d = other.getD();
19            if (this$d == null) {
20                if (other$d != null) {
21                    return false;
22                }
23            } else if (!this$d.equals(other$d)) {
24                return false;
25            }
26
27            if (this.getE() != other.getE()) {
28                return false;
29            } else if (this.getF() != other.getF()) {
30                return false;
31            } else {
32                Object this$g = this.getG();
33                Object other$g = other.getG();
34                if (this$g == null) {
35                    if (other$g != null) {
36                        return false;
37                    }
38                } else if (!this$g.equals(other$g)) {
39                    return false;
40                }
41
42                return true;
43            }
44        }
45    }
46}
47
48protected boolean canEqual(Object other) {
49    return other instanceof Example;
50}
51
52public int hashCode() {
53    int PRIME = true;
54    int result = 1;
55    int result = result * 59 + this.getA();
56    result = result * 59 + Float.floatToIntBits(this.getB());
57    long $c = Double.doubleToLongBits(this.getC());
58    result = result * 59 + (int)($c >>> 32 ^ $c);
59    Object $d = this.getD();
60    result = result * 59 + ($d == null ? 43 : $d.hashCode());
61    result = result * 59 + this.getE();
62    result = result * 59 + this.getF();
63    Object $g = this.getG();
64    result = result * 59 + ($g == null ? 43 : $g.hashCode());
65    return result;
66}

大致和前两种行为一致,这里选择素数从 31 替换成了 59,没有太大差异。

总结

我在开发时也曾考虑一个问题:一个数据库持久化对象到底怎么正确覆盖 hashCode 和 equals?以订单为例,是用主键 id 来判断,还是 流水编号 orderNo 来判断,可能没有准确的答案,各有各的道理,但如果将它丢进 HashSet,HashMap 中就要额外注意,hashCode 和 equals 会影响它们的行为!

这次 Lombok 发生的惨案主要还是由于不合理的 hashCode 和 equals(也包括了 toString)方法导致的,循环引用这种问题虽然没有直接在《effective java》中介绍,但一个引用,一个集合类是不是应该作为 hashCode 和 equals 的关键域参与计算,还是值得开发者仔细推敲的。本文还介绍了一些 hashCode 和 equals 的通用原则,弱弱地推荐 Lombok 便捷开发,强烈安利《effective java》一书。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kirito的技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
面试题:重写equals方法为什么通常会重写hashcode方法?
最近在面试的时候,当问完了HashMap的数据结构之后,通常会再多问一个问题,就是:重写equals方法时通常为什么也要重写一下hashcode方法?
程序新视界
2021/03/22
7130
Java基础篇:什么是hashCode 以及 hashCode()与equals()的联系
hashCode就是对象的散列码,是根据对象的某些信息推导出的一个整数值,默认情况下表示是对象的存储地址。通过散列码,可以提高检索的效率,主要用于在散列存储结构中快速确定对象的存储地址,如Hashtable、hashMap中。
全栈程序员站长
2021/04/13
2.4K0
Java基础篇:什么是hashCode 以及 hashCode()与equals()的联系
equals和hashCode你学会了么?
作为在Object中的equals方法和hashCode方法,或多或少我们在子类中都有重写过这两个方法,那么我们在重写这两个方法时需要注意些什么?就让我们通过这篇文章来聊一聊。
shysh95
2020/08/07
7410
java为什么要重写hashCode和equals方法
      在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等。在这种情况下,原生的equals方法就不能满足我们的需求了。
业余草
2019/01/21
3K0
Effective.Java 读书笔记(9)关于HashCode
有很多程序会错误的原因之一,就是当你重写一个类的equals方法的时候忘记重写它的hashCode了
Mezereon
2018/09/13
3010
​第3章 对于所有对象都通用的方法
现在技术圈特别浮躁,也很容易被感染,看了很多文章很多博客,最后发现自己依然不懂,再加上每天信息爆炸,就特别容易浮躁
程序亦非猿
2019/08/16
5690
一次性搞清楚equals和hashCode
在程序设计中,有很多的“公约”,遵守约定去实现你的代码,会让你避开很多坑,这些公约是前人总结出来的设计规范。
java架构师
2019/05/22
6440
Java开发神器Lombok的安装与使用
项目中经常使用bean,entity等类,绝大部分数据类类中都需要get、set、toString、equals和hashCode方法,虽然eclipse和idea开发环境下都有自动生成的快捷方式,但自动生成这些代码后,如果bean中的属性一旦有修改、删除或增加时,需要重新生成或删除get/set等方法,给代码维护增加负担。而使用了lombok则不一样,使用了lombok的注解(@Setter,@Getter,@ToString,@@RequiredArgsConstructor,@EqualsAndHashCode或@Data)之后,就不需要编写或生成get/set等方法,很大程度上减少了代码量,而且减少了代码维护的负担。故强烈建议项目中使用lombok,去掉bean中get、set、toString、equals和hashCode等方法的代码。
秃头哥编程
2019/06/17
5930
Java开发神器Lombok的安装与使用
Lombok介绍、使用方法和总结
1 Lombok背景介绍 官方介绍如下: Project Lombok makes java a spicier language by adding 'handlers' that know how to build and compile simple, boilerplate-free, not-quite-java code. 大致意思是Lombok通过增加一些“处理程序”,可以让java变得简洁、快速。 2 Lombok使用方法 Lombok能以简单的注解形式来简化java代码,提高开发人员的开发
猿人谷
2018/03/28
1.6K0
Lombok介绍、使用方法和总结
效率编程 之「对于所有对象都通用的方法」
覆盖equals方法看似很简单,但是有许多覆盖方式会导致错误,并且后果非常严重。最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每个实例都只与它自身相等。如果类满足了以下任何一个条件,就不需要我们覆盖equals方法:
CG国斌
2019/05/26
4610
吊打面试官系列:说说hashCode和equals方法
首先我们需要知道hashCode方法和equals方法都是属于Object类的方法。既然属于Object中public修饰的方法,那言外之就是所有对象默认都有这两个方法,只是有时候有的对象已对这两个方法进行了重写。
田维常
2020/11/30
2K0
吊打面试官系列:说说hashCode和equals方法
对Java中HashCode方法的深入思考
最近在学习 Go 语言,Go 语言中有指针对象,一个指针变量指向了一个值的内存地址。学习过 C 语言的猿友应该都知道指针的概念。Go 语言语法与 C 相近,可以说是类 C 的编程语言,所以 Go 语言中有指针也是很正常的。我们可以通过将取地址符&放在一个变量前使用就会得到相应变量的内存地址。
Bug开发工程师
2019/08/31
8790
【Java提高十二】hashCode()equals()
hashCode的作用 要想了解一个方法的内在原理,我们首先需要明白它是干什么的,也就是这个方法的作用。在讲解数组时,我们提到数组是java中效率最高的数据结构,但是“最高”是有前提的。第一我们需要知道所查询数据的所在位置。第二:如果我们进行迭代查找时,数据量一定要小,对于大数据量而言一般推荐集合。 在Java集合中有两类,一类是List,一类是Set他们之间的区别就在于List集合中的元素师有序的,且可以重复,而Set集合中元素是无序不可重复的。对于List好处理,但是对于Set而言我们要如何来保证元
Java帮帮
2018/03/15
8150
【Java提高十二】hashCode()equals()
@Data注解导致的StackOverflowError
Springboot项目中使用Lombok,实体采用@Data注解。运行过程中报Caused by: java.lang.StackOverflowError。
有一只柴犬
2024/01/25
2830
Java 覆盖equals和hashCode方法
覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重,最容易避免这类问题的办法就是不覆盖equals方法。
用户2146693
2019/08/08
8900
Java 覆盖equals和hashCode方法
超级全面的 Lombok 注解介绍,学一波!
blog.csdn.net/riemann_/article/details/105374987
全栈程序员站长
2021/06/29
5.2K0
Java开发的效率工具--Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more. ######################################################## 大概的意思:Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。
框架师
2020/07/09
7560
Java开发的效率工具--Lombok
带你快速入门 Java开发神器 lombok
在写JavaBean对象时候 每次都要重复Alt+insert写Getter/Setter toString 有参构造 和 无参构造 特别麻烦 浪费时间 今天给大家推荐神器lombok 官方支持的神器 可以帮您省去大量时间 让代码简洁易读
陶然同学
2023/02/27
2730
带你快速入门 Java开发神器 lombok
你所不知道的Java之HashCode
以下内容为作者辛苦原创,版权归作者所有,如转载演绎请在“光变”微信公众号留言申请,转载文章请在开始处显著标明出处。
白凡
2018/06/04
7560
你所不知道的Java之HashCode
lombok使用基础教程
前言 lombok是一个编译级别的插件,它可以在项目编译的时候生成一些代码。在很多工具类的项目中都有这个功能。比如dagger。 通俗的说,lombok可以通过注解来标示生成getter settter等代码。我们自然可以通过编译器比如IDEA的Generate生成,为啥要用这个? 在项目开发阶段,一个class的属性是一直变化的,今天可能增加一个字段,明天可能删除一个字段。每次变化都需要修改对应的模板代码。另外,有的class的字段超级多,多到一眼看不完。如果加上模板代码,更难一眼看出来。更有甚者,由于字
Ryan-Miao
2018/03/14
1.7K0
lombok使用基础教程
相关推荐
面试题:重写equals方法为什么通常会重写hashcode方法?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验