容器中的单例集合——Set接口(一)——Set接口以及HashSet容器类

 Set接口简介

        Set接口也是接口Collection的子接口,它继承了Collection接口中所有的抽象方法,并且它和List接口不同,它并没有新增加属于自己的抽象方法,因此学习Set接口时会较为简单。

        那么为什么它没有新增加属于自己的方法呢,这和Set接口定义的容器类的特点有关系。Set接口定义的容器类和List接口定义的容器类不同,它里面的元素是无序且不重复的,也就是说无法通过索引来操纵容器里面的元素,因此不需要新增加有关索引的方法。

        Set接口常用的实现类有两个,分别是HashSet和TreeSet,一般情况下使用HashSet比较多。

HashSet容器类

        HashSet容器的存储原理

        HashSet容器类是Set接口的一个具体实现类,它的底层是通过HashMap来实现的,可以说HashSet就是一个简化版的HashMap。HashSet容器类中没有重复的元素,并且允许出现元素为null的情况,由于它的底层是通过HashMap来实现的,因此它的查询效率和增删效率都比较高。

        在说HashSet的存储方式之前,我们需要先了解一下Hash算法。Hash算法可以用以下的一个例子来简单理解,比如我们要将一组数据存储到一个长度为16的数组当中,这时如果存储有序,那么一般会按照由小到大或者由大到小的顺序来储存,但是要如何实现无序排列呢,Hash算法是这样实现的,将需要储存的数据的值和16取余得到的值就是这个元素在数组中的位置,这样就能将元素在数组中分开储存,但是这里会不会出现一种极端情况呢?就是两个值和16进行取余运算时得到的结果是相同的情况,这时要怎样处理呢。一般情况下如果进行取余运算后得到了元素要存储到数组中的位置,如果该位置已经有元素了,那么存储位置会往下移动一个,如果移动后的位置仍然有元素,那么继续移动,以此类推。这就是Hash算法原理一个比较简单描述。

        在了解Hash算法之后,我们在来谈Hash容器类的存储特征就比较好理解了。HashSet的底层是通过HashMap来实现的,而HashMap的底层是通过数组与链表来实现的。当我们往容器中添加一个元素时,java会调用Object中的HashCode方法,通过这个方法返回一个整数的值,让这个值进行Hash运算,得到的结果就是这个元素在容器中存储的位置。这样就实现了容器中存储的无序性。

        实现了容器中元素的无序性,还要实现元素的不可重复性。在HashSet中,如果两个元素进行Hash算法时得到的结果是不同的,那么这两个元素肯定是不相同的,我们要考虑的就只是通过Hash算法得到的结果相同的情况。因此我们只要在通过Hash算法得到的结果相同时调用equals方法,判断两个元素是否相同即可,如果相同,将原来的元素覆盖,如果不相同,则将其存储到下一个位置,这样就实现了容器中的元素不会有重复的情况。

HashSet容器的使用

        在实例化HashSet容器类时,由于其是Set接口的实现类,因此可以用Set接口来进行实例化,这里要注意,Set接口是泛型接口,在实例化时要给定泛型类型,并且用泛型的方法实例化。在HashSet容器类中,不可以出现重复的元素,但是在添加元素时,如果添加了相同的元素程序仍旧可以照常运行,但是在运行的结果中却不会出先两个相同的元素,这是因为后添加的元素把先添加的元素覆盖了的原因。此外,由于HashSet类中的元素是无序储存的,也就是说元素不存在索引,所以我们不能通过下标对元素进行操作。这也就意味着我们无法通过循环的方式来对元素进行遍历,但可以通过增强for循环来对元素进行遍历。这里可能会有一个疑问,不是说不能通过循环结构来遍历HashSet容器中的元素吗?为什么又能用增强for循环来遍历?这不是自相矛盾吗?其实,增强for循环的本质是一个迭代器,因此使用它并不需要对元素的索引进行操作就能遍历元素。

        在说完上面的情况,我们这里再补充说明在HashSet容器中储存自定义对象时的问题。这里我们先创建一个User类,在这个类中定义了两个私有属性UserName和UserAge,并且提供了相应的set和get方法。构建了User类的全参构造方法和空参构造方法。然后我们新创建一个HashSet类的容器set1来储存这个User类。

        可以发现,当我们没有在User类中重写hashCode方法时,此时我们创建的两个对象u和u1虽然内容完全一致,但是两个对象都添加进了容器set1中。打印它们的hashCode的值能发现它们的值并不相同,而我们在User类中重写了hashCode方法和equals方法后,它们的hashCode的值变成一样的了,所以这时只能储存一个对象。因此当我们在HashSet容器中储存自创建的对象时,要在该对象对应的类中重写hashCode方法和equals方法,不然无法满足HashSet容器的存储要求。

        关于上述的HashSet类的使用,详细可参考一下演示代码:

package com.container.demo;
import  java.util.Set;
import  java.util.HashSet;

public class HashSetTest {
    public static  void main(String[] args) {
        Set<String> set = new HashSet<>();
        //添加元素
        set.add("a");
        set.add("b1");
        set.add("c2");
        set.add("d");
        set.add("a");//定义重复的元素时,程序能通过编译,且能运行,但一个set容器中不会出现两个相同的元素

        //获取元素,没有索引,所以没有get方法,且无法通过普通for循环获取元素
        for (String str:set
             ) {
            System.out.println(str);
        }
        System.out.println("___________________________");
        boolean flag = set.remove("a");
        System.out.println(flag);
        for (String str:set
             ) {
            System.out.println(str);
        }
        System.out.println(set.size());
        System.out.println("_________________________");

        //实例化HashSet
        Set<User> set1 = new HashSet<User>() ;
        User user = new User("linyi",18);
        User user1 = new User("linyi",18);
        set1.add(user);
        set1.add(user1);
        System.out.println(user.hashCode());
        System.out.println(user1.hashCode());
        for (User users:set1
             ) {
            System.out.println(users);
        }

    }
}
package com.container.demo;

import java.util.Objects;

public class User implements Comparable<User>{
    private String usersName;
    private int usersAge;

    public String getUsersName() {
        return usersName;
    }

    public void setUsersName(String usersName) {
        this.usersName = usersName;
    }

    public int getUsersAge() {
        return usersAge;
    }

    public void setUsersAge(int usersAge) {
        this.usersAge = usersAge;
    }

    @Override
    public String toString() {
        return "User{" +
                "usersName='" + usersName + '\'' +
                ", usersAge=" + usersAge +
                '}';
    }

    public User(String usersName, int usersAge) {
        this.usersName = usersName;
        this.usersAge = usersAge;
    }

    public User() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return usersAge == user.usersAge && Objects.equals(usersName, user.usersName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(usersName, usersAge);
    }

}

      

未重写hachCode方法时的结果
未重写hashCode方法和wquals方法时的结果

  

重写hashCode方法和equals方法之后的运行结果

        

        

  • 42
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值