Java的contains()方法实现原理——equals(),hashcode() 实例分析

本文通过详尽测试,探究了Java中不同容器(List、Set、Map)使用contains方法时的底层实现原理,特别是针对自定义对象时的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java编程离不开容器,使用容器又会常常用到contains()方法,这篇文章通过极其详细的测试,探究了 java 中 contains() 方法实现的底层原理。

第一波测试
  • String 和自定义 object 类之间储存方式的区别
  • String 和 object在使用 contains 时的差别
  • List Set Map 的不同表现
//注释为false的是错误,没写的是true

import java.util.*;

import javax.print.attribute.HashAttributeSet;

public class contains{
    public static void main(String[] args){

        String str1 = "aaa";
        String str11 = "aaa";
        String str2 = "bbb";

        //string 存储方式,重复字符串不会另行开辟空间
        System.out.println("String storage test:");
        System.out.println(str1 == "aaa");
        System.out.println(str1 == str11);
        System.out.println(str1.equals(str2));// false
        System.out.println(str1.equals("aaa"));

        Person p1 = new Person("A", 1);
        Person p11 = new Person("A", 1);
        Person p2 = new Person("B", 2);

        //new几个就有几个空间
        System.out.println("\nObject storage test");
        System.out.println(p1 == p11); // false
        System.out.println(p1.equals(p11));// false

        //list string
        List<String> list1 = new ArrayList<>();
        list1.add(str1);
        System.out.println("\nList String test:");
        System.out.println(list1.contains(str1));
        System.out.println(list1.contains("aaa"));
        System.out.println(list1.contains(str11));

        //list object
        List<Person> list2 = new ArrayList<>();
        list2.add(p1);
        System.out.println("\nList object test:");
        System.out.println(list2.contains(p1));
        System.out.println(list2.contains(p11));// false
        System.out.println(list2.contains(p2));// false

        //set string
        Set<String> set1 = new HashSet<String>();
        set1.add(str1);
        System.out.println("\nSet String test:");
        System.out.println(set1.contains(str1));
        System.out.println(set1.contains("aaa"));
        System.out.println(set1.contains(str11));

        //set object
        Set<Person> set2 = new HashSet<Person>();
        set2.add(p1);
        System.out.println("\nSet Object test:");
        System.out.println(set2.contains(p1));
        System.out.println(set2.contains(p11));// false
        System.out.println(set2.contains(p2));// false

        //map string
        Map<String,String> map1 = new HashMap<String, String>();
        map1.put(str1, str1);
        System.out.println("\nMap String test:");
        System.out.println(map1.containsKey(str1));
        System.out.println(map1.containsKey(str11));
        System.out.println(map1.containsValue(str1));
        System.out.println(map1.containsValue(str11));

        //map object
        Map<Person,Person> map2 = new HashMap<Person,Person>();
        map2.put(p1,p1);
        System.out.println("\nMap Object test:");
        System.out.println(map2.containsKey(p1));
        System.out.println(map2.containsKey(p11));// false
        System.out.println(map2.containsValue(p1));
        System.out.println(map2.containsValue(p11));// false
    }
}

class Person{
    private String name;
    private int age;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }
}

可以看出,String 只要内容相同就被判断为 true

第二波测试

重写Person的equals方法之后的结果:

//注释为false的是错误,没写的是true

import java.util.*;

import javax.print.attribute.HashAttributeSet;

public class contains{
    public static void main(String[] args){

        String str1 = "aaa";
        String str11 = "aaa";
        String str2 = "bbb";

        //string 存储方式,重复字符串不会另行开辟空间
        System.out.println("String storage test:");
        System.out.println(str1 == "aaa");
        System.out.println(str1 == str11);
        System.out.println(str1.equals(str2));// false
        System.out.println(str1.equals("aaa"));

        Person p1 = new Person("A", 1);
        Person p11 = new Person("A", 1);
        Person p2 = new Person("B", 2);

        //new几个就有几个空间
        System.out.println("\nObject storage test");
        System.out.println(p1 == p11); // false
        System.out.println(p1.equals(p11));

        //list string
        List<String> list1 = new ArrayList<>();
        list1.add(str1);
        System.out.println("\nList String test:");
        System.out.println(list1.contains(str1));
        System.out.println(list1.contains("aaa"));
        System.out.println(list1.contains(str11));

        //list object
        List<Person> list2 = new ArrayList<>();
        list2.add(p1);
        System.out.println("\nList object test:");
        System.out.println(list2.contains(p1));
        System.out.println(list2.contains(p11));
        System.out.println(list2.contains(p2));// false

        //set string
        Set<String> set1 = new HashSet<String>();
        set1.add(str1);
        System.out.println("\nSet String test:");
        System.out.println(set1.contains(str1));
        System.out.println(set1.contains("aaa"));
        System.out.println(set1.contains(str11));

        //set object
        Set<Person> set2 = new HashSet<Person>();
        set2.add(p1);
        System.out.println("\nSet Object test:");
        System.out.println(set2.contains(p1));
        System.out.println(set2.contains(p11));// false
        System.out.println(set2.contains(p2));// false

        //map string
        Map<String,String> map1 = new HashMap<String, String>();
        map1.put(str1, str1);
        System.out.println("\nMap String test:");
        System.out.println(map1.containsKey(str1));
        System.out.println(map1.containsKey(str11));
        System.out.println(map1.containsValue(str1));
        System.out.println(map1.containsValue(str11));

        //map object
        Map<Person,Person> map2 = new HashMap<Person,Person>();
        map2.put(p1,p1);
        System.out.println("\nMap Object test:");
        System.out.println(map2.containsKey(p1));
        System.out.println(map2.containsKey(p11));// false
        System.out.println(map2.containsValue(p1));
        System.out.println(map2.containsValue(p11));
    }
}

class Person{
    private String name;
    private int age;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Person){
            if (this.getName().equals(((Person)obj).getName())
            &&(this.getAge() == ((Person)obj).getAge())){
                return true;
            }
        }
        return false;
    }
}

不出所料的是,不同的拥有相同内容的Person可以equal,list 的contains 能够识别内容相同的 Person,然而 set 却没有变化 ,map 的 containsValue 能够识别内容相同的 Person,而containsKey 却不能。
这是一个很有趣的结果
原因是,set 和 map 的 containskey 在使用equals的同时,也会使用 hashCode() 方法,因此,两个方法返回都相同时,两个对象才能被视为相等。

第三波测试

重写hashCode() 方法:

//注释为false的是错误,没写的是true

import java.util.*;

import javax.print.attribute.HashAttributeSet;

public class contains{
    public static void main(String[] args){

        String str1 = "aaa";
        String str11 = "aaa";
        String str2 = "bbb";

        //string 存储方式,重复字符串不会另行开辟空间
        System.out.println("String storage test:");
        System.out.println(str1 == "aaa");
        System.out.println(str1 == str11);
        System.out.println(str1.equals(str2));// false
        System.out.println(str1.equals("aaa"));

        Person p1 = new Person("A", 1);
        Person p11 = new Person("A", 1);
        Person p2 = new Person("B", 2);

        //new几个就有几个空间
        System.out.println("\nObject storage test");
        System.out.println(p1 == p11); // false
        System.out.println(p1.equals(p11));

        //list string
        List<String> list1 = new ArrayList<>();
        list1.add(str1);
        System.out.println("\nList String test:");
        System.out.println(list1.contains(str1));
        System.out.println(list1.contains("aaa"));
        System.out.println(list1.contains(str11));

        //list object
        List<Person> list2 = new ArrayList<>();
        list2.add(p1);
        System.out.println("\nList object test:");
        System.out.println(list2.contains(p1));
        System.out.println(list2.contains(p11));
        System.out.println(list2.contains(p2));// false

        //set string
        Set<String> set1 = new HashSet<String>();
        set1.add(str1);
        System.out.println("\nSet String test:");
        System.out.println(set1.contains(str1));
        System.out.println(set1.contains("aaa"));
        System.out.println(set1.contains(str11));

        //set object
        Set<Person> set2 = new HashSet<Person>();
        set2.add(p1);
        System.out.println("\nSet Object test:");
        System.out.println(set2.contains(p1));
        System.out.println(set2.contains(p11));
        System.out.println(set2.contains(p2));// false

        //map string
        Map<String,String> map1 = new HashMap<String, String>();
        map1.put(str1, str1);
        System.out.println("\nMap String test:");
        System.out.println(map1.containsKey(str1));
        System.out.println(map1.containsKey(str11));
        System.out.println(map1.containsValue(str1));
        System.out.println(map1.containsValue(str11));

        //map object
        Map<Person,Person> map2 = new HashMap<Person,Person>();
        map2.put(p1,p1);
        System.out.println("\nMap Object test:");
        System.out.println(map2.containsKey(p1));
        System.out.println(map2.containsKey(p11));
        System.out.println(map2.containsValue(p1));
        System.out.println(map2.containsValue(p11));
    }
}

class Person{
    private String name;
    private int age;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Person){
            if (this.getName().equals(((Person)obj).getName())
            &&(this.getAge() == ((Person)obj).getAge())){
                return true;
            }
        }
        return false;
    }
    @Override
    public int hashCode() {
        return this.getAge();
    }
}

结论:set 的contains 和 map 的 containskey 在使用equals的同时,也会先使用 hashCode() 方法,在hashCode 相同的基础上再通过equals 判断是否相同。两个判断都相同时,才认定元素相等,contains 返回 true

后话:之后的学习中也印证了,set 的 contains 使用hashcode 方法判断等价性是因为,上面例子中使用的是 hashset ,也就是,set 的下层实现方式是哈希,所以才会跟 hashcode 扯上关系。同理,map 也因为 hashmap 底层的哈希原理。

### Java 中 `HashSet` 的使用教程 #### 什么是 `HashSet` `HashSet` 是基于哈希表的 `Set` 实现,不允许存储重复元素。该数据结构不保证元素的顺序,并且大部分方法依赖于底层的 `HashMap` 来完成操作[^1]。 #### 初始化方式 存在多种构造器用于创建 `HashSet` 对象: - **无参数构造器**:这是最简单的初始化形式,它会默认设置初始容量和加载因子。 ```java Set<Integer> set = new HashSet<>(); ``` - **带有指定集合作为参数的构造器**:可以将已有的集合转换成 `HashSet`。 ```java Collection<String> collection = Arrays.asList("apple", "banana"); Set<String> fruitSet = new HashSet<>(collection); ``` - **带自定义负载因子或容量大小的构造器**:允许开发者更精细地控制内部数组的增长行为。 ```java // 定义初始容量为20, 负载因子为0.75f Set<Double> numbers = new HashSet<>(20, 0.75f); ``` #### 添加与移除元素 向 `HashSet` 插入新成员非常简单,只需调用 `add()` 方法即可;而要删除某个特定项,则可利用 `remove()` 函数。 ```java // 向集合中添加三个整数 set.add(1); set.add(2); set.add(3); // 移除其中一个数值 if (set.contains(2)) { set.remove(2); } ``` #### 判断唯一性和处理冲突 当尝试加入新的对象时,`HashSet` 将依据此对象的 `hashCode()` 返回值决定其位置索引。为了确保不同实例不会被误认为相同,还需要覆写 `equals(Object obj)` 方法来进行最终比较。如果两个对象具有相同的散列码却并不相等(即它们代表不同的实体),那么这些条目会被放置在同一桶位上形成链表或者红黑树以解决潜在的碰撞问题[^3]。 #### 时间复杂度分析 通常情况下,由于采用了优秀的哈希算法设计,使得增删查改的操作效率接近常量时间 O(1),但在某些特殊条件下——比如大量键值产生了严重的哈希冲突——性能可能退化至线性级别 O(n)[^4]。 #### 示例代码展示 下面给出一段完整的例子演示如何运用上述知识点构建并操控一个基本类型的 `HashSet`: ```java import java.util.HashSet; import java.util.Set; public class Main { public static void main(String[] args) { // 创建一个新的HashSet实例 Set<Integer> myNumbers = new HashSet<>(); // 增加几个数字到这个集中 myNumbers.add(10); myNumbers.add(20); myNumbers.add(30); System.out.println("当前集合中的元素:" + myNumbers); // 测试是否包含某具体值 boolean hasNumber = myNumbers.contains(20); System.out.println("是否存在20? " + hasNumber); // 清理掉之前存在的那个数 if(hasNumber){ myNumbers.remove(20); } System.out.println("清理后的状态:" + myNumbers); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值