电子书 – Spring Cloud 指南 – NPI EA (分类=Spring Cloud)
announcement - icon

让我们开始使用 Spring Cloud 的微服务架构

>> 加入 Pro 并下载电子书

电子书 – Mockito – NPI EA (标签 = Mockito)
announcement - icon

模拟是单元测试的重要组成部分,Mockito 库使编写 清晰直观的单元测试 变得容易,用于您的 Java 代码。

通过我们的 Mockito 指南 开始模拟,并改进您的应用程序测试

下载电子书

电子书 – Java 并发 – NPI EA (分类=Java 并发)
announcement - icon

在应用程序中处理并发可能是一个棘手的过程,其中包含许多 潜在的陷阱。 扎实的掌握基本知识将有助于最大程度地减少这些问题。

通过我们的 Java 并发 指南开始了解多线程应用程序

>> 下载电子书

电子书 – 响应式 – NPI EA (分类=响应式)
announcement - icon

Spring 5 增加了对使用 Spring WebFlux 模块进行响应式编程的支持,此支持自那时起不断改进。 开始使用 Reactor 项目基础知识和 Spring Boot 中的响应式编程

>> 加入 Pro 并下载电子书

电子书 – Java Streams – NPI EA (分类=Java Streams)
announcement - icon

自从 Java 8 引入以来,Stream API 已成为 Java 开发的基础。 基本操作,例如迭代、过滤、映射元素序列,使用起来看似很简单。

但这些也可能被过度使用并陷入一些常见陷阱。

更好地了解 Stream 的工作方式 以及如何将其与其他语言功能结合使用,请查看我们关于 Java Streams 的指南

>> 加入 Pro 并下载电子书

电子书 – Jackson – NPI EA (分类=Jackson)
announcement - icon

用 Jackson 正确处理 JSON

下载电子书

电子书 – HTTP 客户端 – NPI EA (分类=Http 客户端)
announcement - icon

充分利用 Apache HTTP 客户端

下载电子书

电子书 – Maven – NPI EA (分类 = Maven)
announcement - icon

开始使用 Apache Maven

下载电子书

电子书 – 持久化 – NPI EA (分类=持久化)
announcement - icon

您在努力实现正确的持久化层 Spring 吗?

探索电子书

电子书 – RwS – NPI EA (分类=Spring MVC)
announcement - icon

使用 Spring 构建 REST API 吗?

下载电子书

课程 – LS – NPI EA (分类=Jackson)
announcement - icon

通过 Learn Spring 课程开始学习 Spring 和 Spring Boot

>> 学习 SPRING
课程 – RWSB – NPI EA (分类=REST)
announcement - icon

通过构建一个完整的 REST API,深入了解 Spring Boot 3 和 Spring 6,使用该框架

>> 全新的“REST With Spring Boot”

课程 – LSS – NPI EA (分类=Spring Security)
announcement - icon

是的,Spring Security 可能很复杂,从核心内的更高级功能到框架中深入的 OAuth 支持。

我将安全材料构建为 两个完整的课程 - 核心和 OAuth,以针对这些更复杂的场景进行实践。 我们探索何时以及如何使用每个功能,并 在后台项目中对其进行编码

您可以在这里探索该课程

>> 学习 Spring Security

课程 – LSD – NPI EA (标签=Spring Data JPA)
announcement - icon

Spring Data JPA 是处理 JPA 复杂性的绝佳方式,它具有 Spring Boot 的强大简洁性

通过引导式参考课程开始使用 Spring Data JPA

>> 查看课程

合作伙伴 – Moderne – NPI EA (类别=Spring Boot)
announcement - icon

使用 OpenRewrite 安全且自动地重构 Java 代码。

手动重构大型代码库既缓慢、有风险,又容易拖延。OpenRewrite 应运而生。这个用于大规模、自动化代码转换的开源框架可以帮助团队安全、一致地进行现代化改造。

每个月,OpenRewrite 的创建者和维护者 Moderne 都会举办现场、实践培训课程——一个面向初学者,一个面向经验丰富的用户。您将了解配方的运作方式、如何将其应用于项目,以及如何自信地进行代码现代化改造。

参加下一次课程,带来您的问题,并学习如何自动化通常会占用您 sprint 时间的工作。

合作伙伴 – LambdaTest – NPI EA (类别=测试)
announcement - icon

回归测试是发布流程中的重要步骤,以确保新代码不会破坏现有功能。随着代码库的不断发展,我们希望频繁运行这些测试,以便尽早发现任何问题。

确保这些测试以自动化的方式频繁运行的最佳方法当然是将其包含在 CI/CD 管道中。 这样,每次向仓库提交代码时,回归测试将自动执行。

在本教程中,我们将学习如何使用 Selenium 创建回归测试,然后使用 GitHub Actions 将它们包含在我们的管道中,在 LambdaTest 云网格上运行

>> 如何使用 GitHub Actions 运行 Selenium 回归测试

课程 – LJB – NPI EA (类别 = Core Java)
announcement - icon

通过编码方式构建 Java 的坚实、实用的基础

>> 学习 Java 基础

1. 概述

哈希是计算机科学的一个基本概念。

在 Java 中,高效的哈希算法支撑着一些最流行的集合,例如 HashMap(查看这个深入的 文章)和 HashSet

在本教程中,我们将重点介绍 hashCode() 的工作原理,它在集合中的作用以及如何正确实现它。

更多阅读

Java equals() 和 hashCode() 契约

了解 equals() 和 hashCode() 需要满足的契约以及这两个方法之间的关系

使用 Eclipse 生成 equals() 和 hashCode()

使用 Eclipse IDE 生成 equals() 和 hashCode() 的快速实用指南

Project Lombok 介绍

对 Project Lombok 在标准 Java 代码中许多有用用例的全面而实用的介绍。

2. 在数据结构中使用 hashCode()

在某些情况下,集合上的最简单操作也可能效率低下。

为了说明,这将触发线性搜索,这对于巨大的列表来说效率非常低。

List<String> words = Arrays.asList("Welcome", "to", "Baeldung");
if (words.contains("Baeldung")) {
    System.out.println("Baeldung is in the list");
}

Java 提供了一些数据结构来专门处理这个问题。 例如,几个 Map 接口实现是 哈希表

在使用哈希表时,这些集合使用 hashCode() 方法计算给定键的哈希值。 然后它们在内部使用这个值来存储数据,以便访问操作更加高效。

3. 理解 hashCode() 的工作原理

简单地说,hashCode() 返回一个整数值,由哈希算法生成。

相等(根据它们的 equals())的对象必须返回相同的哈希码。 不同的对象不需要返回不同的哈希码。

hashCode() 的一般契约规定

  • 在 Java 应用程序执行期间多次调用同一个对象上的 hashCode() 时,只要用于对象相等比较的信息没有修改,hashCode() 必须始终返回相同的值。 此值不需要在应用程序的一次执行到另一次执行之间保持一致。
  • 如果两个对象根据 equals(Object) 方法相等,则在每个对象上调用 hashCode() 方法必须产生相同的值。
  • 如果两个对象根据 equals(java.lang.Object) 方法不相等,则在每个对象上调用 hashCode 方法不需要产生不同的整数结果。 但是,开发人员应该意识到,为不相等的对象生成不同的整数结果可以提高哈希表的性能。

“在实践中尽可能地,由类 Object 定义的 hashCode() 方法确实为不同的对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)”

4. 一个朴素的 hashCode() 实现

一个朴素的hashCode() 实现,完全符合上述约定,实际上非常简单。

为了演示这一点,我们将定义一个示例 User 类,该类重写了方法的默认实现。

public class User {

    private long id;
    private String name;
    private String email;

    // standard getters/setters/constructors
        
    @Override
    public int hashCode() {
        return 1;
    }
        
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;
        if (this.getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id 
          && (name.equals(user.name) 
          && email.equals(user.email));
    }
    
    // getters and setters here
}

User 类为 equals()hashCode() 都提供了自定义实现,完全符合各自的约定。更重要的是,hashCode() 返回任何固定值都没有问题。

但是,这种实现会将哈希表的功能降至基本为零,因为每个对象都会存储在同一个桶中。

在这种情况下,哈希表查找将线性进行,不会给我们带来任何实际优势。我们在第 7 节中会对此进行更深入的讨论。

5. 改进 hashCode() 实现

让我们通过包含 User 类中的所有字段来改进当前的 hashCode() 实现,以便它能够为不相等的对象生成不同的结果。

@Override
public int hashCode() {
    return (int) id * name.hashCode() * email.hashCode();
}

这个基本的哈希算法肯定比前一个好得多。因为它可以仅通过将 nameemail 字段的哈希码以及 id 相乘来计算对象的哈希码。

一般来说,只要使 equals() 实现与其保持一致,我们可以说这是一个合理的 hashCode() 实现。

6. 标准 hashCode() 实现

我们用于计算哈希码的哈希算法越好,哈希表的性能就越好。

让我们看看一个“标准”实现,它使用两个质数来为计算出的哈希码添加更多的唯一性。

@Override
public int hashCode() {
    int hash = 7;
    hash = 31 * hash + (int) id;
    hash = 31 * hash + (name == null ? 0 : name.hashCode());
    hash = 31 * hash + (email == null ? 0 : email.hashCode());
    return hash;
}

虽然我们需要了解 hashCode()equals() 方法的作用,但不必每次都从头开始实现它们。因为大多数 IDE 都可以生成自定义的 hashCode()equals() 实现。并且自 Java 7 以来,我们有一个 Objects.hash() 工具方法来进行方便的哈希处理。

Objects.hash(name, email)

IntelliJ IDEA 生成以下实现:

@Override
public int hashCode() {
    int result = (int) (id ^ (id >>> 32));
    result = 31 * result + name.hashCode();
    result = 31 * result + email.hashCode();
    return result;
}

Eclipse 生成如下实现:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((email == null) ? 0 : email.hashCode());
    result = prime * result + (int) (id ^ (id >>> 32));
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

除了基于 IDE 的 hashCode() 实现之外,还可以自动生成高效的实现,例如使用 Lombok

在这种情况下,我们需要将 lombok 依赖项添加到 pom.xml 中。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>

现在只需使用 @EqualsAndHashCode 注解 User 类即可。

@EqualsAndHashCode 
public class User {
    // fields and methods here
}

同样,如果我们希望 Apache Commons Lang 的 HashCodeBuilder 为我们生成 hashCode() 实现,我们将在 pom 文件中包含 commons-lang Maven 依赖项。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

并且 hashCode() 可以这样实现:

public class User {
    public int hashCode() {
        return new HashCodeBuilder(17, 37).
        append(id).
        append(name).
        append(email).
        toHashCode();
    }
}

一般来说,在实现 hashCode() 时没有通用的方法。我们强烈建议阅读 Joshua Bloch 的 Effective Java。它提供了一系列 彻底的指南,用于实现高效的哈希算法。

请注意,所有这些实现都在某种程度上利用了数字 31。因为 31 具有一个很好的特性。它的乘法可以被位移运算代替,而位移运算比标准乘法更快。

31 * i == (i << 5) - i

6.1. 获取重写 hashCode()Object 的唯一 ID

当我们即使在 hashCode() 方法被覆盖的情况下,需要检索对象的基于身份的哈希值时,Java 提供了 System.identityHashCode(Object) 方法。它返回 Object 的默认 hashCode() 实现会返回的哈希码,忽略覆盖。

@Test
void givenOverriddenHashCode_whenUsingIdentityHashCode_thenJvmIdentityIsPreserved() {
    User user1 = new User(1L, "John", "[email protected]");
    User user2 = new User(1L, "John", "[email protected]");

    assertEquals(user1.hashCode(), user2.hashCode());

    assertNotEquals(
      System.identityHashCode(user1),
      System.identityHashCode(user2)
    );
}

例如,这对于调试、低级缓存或独立于逻辑相等性跟踪对象实例非常有用。

7. 处理哈希冲突

哈希表的内在行为提出了一种与这些数据结构相关的方面:即使使用高效的哈希算法,两个或多个对象也可能具有相同的哈希码,即使它们不相等。因此,它们的哈希码会指向同一个桶,即使它们具有不同的哈希表键。

这种情况通常被称为哈希冲突,并且存在各种处理它的方法,每种方法都有其优缺点。Java 的 HashMap 使用 分离链接法来处理冲突

“当两个或多个对象指向同一个桶时,它们被简单地存储在链表中。在这种情况下,哈希表是链表数组,并且具有相同哈希码的每个对象都附加到数组中桶索引处的链表中。”

在最坏的情况下,几个桶会绑定到它,并且在列表中检索对象将线性执行。”

哈希冲突方法论简而言之说明了为什么高效地实现 hashCode() 如此重要

Java 8 为 HashMap 实现带来了一个有趣的 增强功能。如果桶的大小超过某个阈值,则树图将取代链表。这允许实现 O(logn) 查找,而不是悲观的 O(n)

8. 创建一个简单的应用程序

现在我们将测试标准 hashCode() 实现的功能。

让我们创建一个简单的 Java 应用程序,将一些 User 对象添加到 HashMap 中,并使用 SLF4J 在每次调用该方法时将消息记录到控制台。

这是示例应用程序的入口点

public class Application {

    public static void main(String[] args) {
        Map<User, User> users = new HashMap<>();
        User user1 = new User(1L, "John", "[email protected]");
        User user2 = new User(2L, "Jennifer", "[email protected]");
        User user3 = new User(3L, "Mary", "[email protected]");

        users.put(user1, user1);
        users.put(user2, user2);
        users.put(user3, user3);
        if (users.containsKey(user1)) {
            System.out.print("User found in the collection");
        }
    }
}

这是 hashCode() 实现

public class User {

    // ...

    public int hashCode() {
        int hash = 7;
        hash = 31 * hash + (int) id;
        hash = 31 * hash + (name == null ? 0 : name.hashCode());
        hash = 31 * hash + (email == null ? 0 : email.hashCode());
        logger.info("hashCode() called - Computed hash: " + hash);
        return hash;
    }
}

这里,重要的是要注意,每次将对象存储在哈希图中并使用 containsKey() 方法检查时,hashCode() 都会被调用,并且计算出的哈希码会打印到控制台

[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: 1255477819
[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: -282948472
[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: -1540702691
[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: 1255477819
User found in the collection

9. 结论

显然,产生高效的 hashCode() 实现通常需要混合一些数学概念(即素数和任意数)、逻辑和基本的数学运算。

无论如何,我们可以在不诉诸这些技术的情况下有效地实现 hashCode()。我们只需要确保哈希算法为不相等的对象产生不同的哈希码,并且它与 equals() 的实现保持一致。

支持本文的代码可在 GitHub 上获取。 一旦你Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。
Baeldung Pro – NPI EA (类别 = Baeldung)
announcement - icon

Baeldung Pro 具有完全无广告以及最终具有深色模式,提供干净的学习体验

>> 探索干净的 Baeldung

一旦早期采用者的席位全部用完,价格将上涨并保持在每年 33 美元。

电子书 – HTTP 客户端 – NPI EA (类别=HTTP 客户端)
announcement - icon

Apache HTTP Client 是一个非常强大的库,适用于简单和高级用例,在测试 HTTP 端点时尤其适用。 查看我们的指南,涵盖基本请求和响应处理,以及安全性、Cookie、超时等。

>> 下载电子书

电子书 – Java 并发 – NPI EA (分类=Java 并发)
announcement - icon

在应用程序中处理并发可能是一个棘手的过程,其中包含许多 潜在的陷阱。 扎实的掌握基本知识将有助于最大程度地减少这些问题。

通过我们的 Java 并发 指南开始了解多线程应用程序

>> 下载电子书

电子书 – Java Streams – NPI EA (分类=Java Streams)
announcement - icon

自从 Java 8 引入以来,Stream API 已成为 Java 开发的基础。 基本操作,例如迭代、过滤、映射元素序列,使用起来看似很简单。

但这些也可能被过度使用并陷入一些常见陷阱。

更好地了解 Stream 的工作方式 以及如何将其与其他语言功能结合使用,请查看我们关于 Java Streams 的指南

>> 加入 Pro 并下载电子书

电子书 – 持久化 – NPI EA (分类=持久化)
announcement - icon

您在努力实现正确的持久化层 Spring 吗?

探索电子书

课程 – LS – NPI EA (类别=REST)

announcement - icon

从 Spring Boot 开始,通过 Learn Spring 课程了解核心 Spring。

>> 查看课程

合作伙伴 – Moderne – NPI EA (标签=重构)
announcement - icon

现代 Java 团队行动迅速——但代码库并不总是跟上。 框架会发生变化,依赖关系会漂移,技术债务会累积,直到它开始拖慢交付速度。 OpenRewrite 就是为此而构建的:一个开源重构引擎,可在保持开发人员意图不变的同时自动化重复的代码更改。

由 Moderne 的 OpenRewrite 创建者和维护者领导的每月培训系列,将介绍实际的迁移和现代化模式。 无论您是重构配方的新手,还是准备编写自己的配方,您都将学习以安全且可扩展的方式进行重构的实用方法。

如果您曾经希望重构感觉像编写代码一样自然——并且一样快速——这是一个很好的起点

电子书 Jackson – NPI EA – 3 (类别 = Jackson)
© .