Java集合——Set详解
来源:阿Q说     阅读:507
源码超市
发布于 2019-06-11 02:38
查看主页

前几天简单详情了一下单列集合中的List,今天就给大家讲一下它的同胞兄弟Set的简介与使用情况。

Set存取无序,元素唯一

代码演示:

public static void demo1() {    HashSet<String> hs = new HashSet<>();   //创立HashSet对象    boolean b1 = hs.add("a");    boolean b2 = hs.add("a");       //当向set集合中存储重复元素的时候返回为false       hs.add("b");    hs.add("c");    hs.add("d");    System.out.println(hs);         //[d, b, c, a] 存取无序 并且去掉了重复元素    System.out.println(b1);         //true    System.out.println(b2);         //false     for (String string : hs) {      //只需能用迭代器迭代的,即可以使用加强for循环遍历        System.out.println(string);    }}

保证元素唯一性需要让元素重写两个方法:一个是 hashCode(),另一个是 equals()。HashSet在存储元素的过程中首先会去调用元素的hashCode()值,看其哈希值与已经存入HashSet的元素的哈希值能否相同,假如不同 :就直接增加到集合;假如相同 :则继续调用元素的equals() 和哈希值相同的这些元素依次去比较。假如说有返回true的,那就重复不增加;假如说比较结果都说false,那就是不重复就增加。为了减少equals的比较次数提高效率一般情况让属性不同的对象尽量hashCode()值不同,那么如何重写 equals()和hashCode()呢? Eclipse自动生成就可。

代码演示:

public static void main(String[] args) {    HashSet<Person> hs = new HashSet<>();    hs.add(new Person("张三", 23));    hs.add(new Person("张三", 23));    hs.add(new Person("李四", 24));    hs.add(new Person("李四", 24));    hs.add(new Person("李四", 24));    hs.add(new Person("李四", 24));            System.out.println(hs);//Person重写了equals()方法和hashCode()方法,所以 hs 去除了重复的元素}public class Person {   //Person里面 Eclipse自动生成了 equals()方法和hashCode()方法    private String name;    private int age;    public Person() {        super();                }    public Person(String name, int age) {        super();        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return "Person [name=" + name + ", age=" + age + "]";    }           /*    * 为什么是31?    * 1,31是一个质数,质数是能被1和自己本身整除的数    * 2,31这个数既不大也不小    * 3,31这个数好算,2的五次方-1,2向左移动4位    */    @Override    public int hashCode() {        final int prime = 31;        int result = 1;        result = prime * result + age;        result = prime * result + ((name == null) ? 0 : name.hashCode());        return result;    }    @Override    public boolean equals(Object obj) {        if (this == obj)                //调用的对象和传入的对象是同一个对象            return true;                //直接返回true        if (obj == null)                //传入的对象为null            return false;               //返回false        if (getClass() != obj.getClass())//判断两个对象对应的字节码文件能否是同一个字节码            return false;               //假如不是直接返回false        Person other = (Person) obj;        //向下转型        if (age != other.age)               //调用对象的年龄不等于传入对象的年龄            return false;               //返回false        if (name == null) {             //调用对象的姓名为null            if (other.name != null)         //传入对象的姓名不为null                return false;           //返回false        } else if (!name.equals(other.name))        //调用对象的姓名不等于传入对象的姓名            return false;               //返回false        return true;                    //返回true    }       }

LinkedHashSet:直接父类是 HashSet

特点:存取有序,存储的元素不能重复。

代码演示:

LinkedHashSet<String> lhs = new LinkedHashSet<>();lhs.add("a");lhs.add("a");lhs.add("a");lhs.add("a");lhs.add("b");lhs.add("c");lhs.add("d");System.out.println(lhs);//[a,b,c,d]  去除了重复的元素,同时又保证了存取有序

TreeSet:元素唯一,更重要的一个是给元素进行排序

方式一:让元素所在的类实现Comparable接口,并重写CompareTo() 方法,并根据CompareTo()的返回值来进行增加元素

举例:

public static void demo2() {    TreeSet<Person> ts = new TreeSet<>();    ts.add(new Person("zhangsan", 23));    ts.add(new Person("lisi", 13));    ts.add(new Person("wangwu", 33));    ts.add(new Person("zhaoliu", 43));    ts.add(new Person("aaaa", 53));    System.out.println(ts);}public class Person implements Comparable<Person> {    private String name;    private int age;    public Person() {        super();    }    public Person(String name, int age) {        super();        this.name = name;        this.age = age;    }    /*    //1):按照年龄排(先比较年龄后比较姓名)    public int compareTo(Person o) {                //this 是集合即将存入的元素, o 是集合已经存入的元素        int num = this.age - o.age; //年龄是比较的主要条件        return num == 0 ? this.name.compareTo(o.name) : num;    //姓名是比较的次要条件    }    */    /*    //2):按照姓名排(先比较姓名后比较年龄)    public int compareTo(Person o) {        int num = this.name.compareTo(o.name);      //姓名是主要条件        return num == 0 ? this.age - o.age : num;   //年龄是次要条件    }    */    //3):按照姓名长度排(先比较姓名长度 后比较姓名内容 后比较年龄)    public int compareTo(Person o) {            int length = this.name.length() - o.name.length();  //比较长度为主要条件        int num = length == 0 ? this.name.compareTo(o.name) : length;//比较内容为次要条件        return num == 0 ? this.age - o.age : num;//比较年龄为次要条件    }   }

方式二: 使用TreeSet的有参构造方法创立TreeSet对象的时候, 传入一个比较器 Comparator 进去, TreeSet在增加元素的时候, 根据比较器的compare()方法的返回值来增加元素。

代码演示:

public static void demo3() {    //在构造函数中传入比较器后 就不用再让Person实现Comparable接口了    TreeSet<Person> ts = new TreeSet<>(new Comparator<Person>() {           //3):按照姓名长度排(先比较姓名长度 后比较姓名内容 后比较年龄)        @Override        public int compare(Person o1, Person o2) {//o1是即将存入的元素  o2是已经存入集合的元素            //比较长度为主要条件            int length = o1.getName().length() - o2.getName().length();             //比较内容为次要条件            int num = length == 0 ? o1.getName().compareTo(o2.getName()) : length;              return num == 0 ? o1.getAge() - o2.getAge() : num;        }        /*        //2):按照姓名排(先比较姓名后比较年龄)        public int compare(Person o1, Person o2) {            int num = o1.getName().compareTo(o2.getName());            return num == 0 ? o1.getAge() - o2.getAge() : num;        }        */        /*        //1):按照年龄排(先比较年龄后比较姓名)        public int compare(Person o1, Person o2) {            int num = o1.getAge() - o2.getAge();            return num == 0 ? o1.getName().compareTo(o2.getName()) : num;        }        */    });    ts.add(new Person("zhangsan", 23));    ts.add(new Person("lisi", 13));    ts.add(new Person("wangwu", 33));    ts.add(new Person("zhaoliu", 43));    ts.add(new Person("aaaa", 53));    System.out.println(ts);}

TreeSet存储元素对元素进行排序的源码解析

TreeSet在存储元素的时候会首先去判断能否有比较器存在(也就是判断比较器能否为null),假如存在:就会让比较器去调用compare(T t1, T t2)方法,去依次比较即将存入的值和已经存入TreeSet集合的值,假如返回正数:往二叉树右侧放;假如返回负数:往二叉树左侧放;假如返回 0 :不增加。假如不存在:底层就会把即将存入的元素自动提升为Comparable类型的对象(所以假如没有比较器的情况下,元素所在的类没有实现Comparable接口,在做自动提升类型的时候就会报类型转换错误),并让该对象调用CompareTo(T t)方法,和已经存入TreeSet集合的元素依次比较,假如返回正数:往二叉树右侧放;假如返回负数:往二叉树左侧放;假如返回 0 :不增加。所以,假如两种方式同时使用,底层会优先使用方式二(比较器的方式)。

源码片段分析:

 public V put(K key, V value) {     //key则是TreeSet即将存入的元素     Entry<K,V> t = root;       //获取TreeSet中已存入的元素列表也就是 t     /*此处有代码省略*/          //comparator是一个成员变量,初始值是null,假如TreeSet构造方法中传入了比较器     //则comparator就不再是null     Comparator<? super K> cpr = comparator;     if (cpr != null) { //假如有比较器 就使用比较器来比较        do {             parent = t;             cmp = cpr.compare(key, t.key); //让比较器 cpr 去调用compare(T t1, T t2)方法,去依次比较即将存入的值 key 和已经存入TreeSet集合的值 t.key             if (cmp < 0)           //假如返回负数                 t = t.left;            //往二叉树左侧放             else if (cmp > 0)      //假如返回正数                 t = t.right;       //往二叉树右侧放             else               //假如返回 0                 return t.setValue(value);  //不增加         } while (t != null);     } else {               //假如没有比较器 则使用自然排序来比较         if (key == null)             throw new NullPointerException();         Comparable<? super K> k = (Comparable<? super K>) key; //把即将存入的元素 key 自动提升为Comparable类型的对象 k (所以假如没有比较器的情况下,元素所在的类没有实现Comparable接口,在做自动提升类型的时候就会报类型转换错误),         do {             parent = t;             cmp = k.compareTo(t.key);  //让即将存入的元素 k 调用CompareTo(T t)方法,和已经存入TreeSet集合的元素 t.key 依次比较             if (cmp < 0)           //假如返回负数                 t = t.left;            //往二叉树左侧放             else if (cmp > 0)      //假如返回正数                 t = t.right;       //往二叉树右侧放             else               //假如返回 0                 return t.setValue(value);  //不增加         } while (t != null);     }     /*此处有代码省略*/ }

案例1:编写一个程序,获取10个1至20的随机数,要求随机数不能重复。并把最终的随机数输出到控制台。

/** - 分析:    - 1,有Random类创立随机数对象    - 2,需要存储10个随机数,而且不能重复,所以我们用HashSet集合    - 3,假如HashSet的size是小于10即可以不断的存储,假如大于等于10就中止存储    - 4,通过Random类中的nextInt(n)方法获取1到20之间的随机数,并将这些随机数存储在HashSet集合中    - 5,遍历HashSet      */public static void main(String[] args) {    //1,有Random类创立随机数对象    Random r = new Random();    //2,需要存储10个随机数,而且不能重复,所以我们用HashSet集合    HashSet<Integer> hs = new HashSet<>();    //3,假如HashSet的size是小于10即可以不断的存储,假如大于等于10就中止存储    while(hs.size() < 10) {        //4,通过Random类中的nextInt(n)方法获取1到20之间的随机数,并将这些随机数存储在HashSet集合中        hs.add(r.nextInt(20) + 1);    }    // 5,遍历HashSet    for (Integer integer : hs) {        System.out.println(integer);    }}

案例2:在一个集合中存储了无序并且重复的字符串,定义一个方法,让其有序(字典顺序),而且还不能去除重复

/** - 分析:    - 1,定义一个List集合,并存储重复的无序的字符串    - 2,定义方法对其排序保留重复    - 3,打印List集合 */public static void main(String[] args) {    //1,定义一个List集合,并存储重复的无序的字符串    ArrayList<String> list = new ArrayList<>();    list.add("aaa");            list.add("aaa");            list.add("ccc");            list.add("ddd");            list.add("fffffffffff");            list.add("heima");            list.add("itcast");            list.add("bbbb");            list.add("aaa");            list.add("aaa");                        //2,定义方法对其排序保留重复           sort(list);                    //3,打印list            System.out.println(list);}       /*         * 定义方法,排序并保留重复         * 分析:         * 1,创立TreeSet集合对象,由于String本身就具有比较功能,但是重复不会保留,所以我们用比较器    * 2,将list集合中所有的元素增加到TrreSet集合中,对其排序,保留重复     * 3,清空list集合    * 4,将TreeSet集合中排好序的元素增加到list中 */    public static void sort(List<String> list) {    //1,创立TreeSet集合对象,由于String本身就具有比较功能,但是重复不会保留,所以我们用比较器          TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {        @Override                    public int compare(String s1, String s2) {            int num = s1.compareTo(s2);            //比较内容为主要条件            return num == 0 ? 1 : num; //保留重复                    }            });         //2,将list集合中所有的元素增加到TrreSet集合中,对其排序,保留重复    ts.addAll(list);           //3,清空list集合            list.clear();            //4,将TreeSet集合中排好序的元素增加到list中            list.addAll(ts);    }

课后习题:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低输出到控制台。

相信大家对Set集合这部分已经有了初步的理解了,那大家就把最后的课后习题完成一下吧。想理解更多学习知识,请关注微信公众号“阿Q说”,获取更多学习资料吧!你也可以后端留言说出你的疑惑,阿Q将会在后期的文章中为你解答。每天学习一点点,每天进步一点点。

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
Android 手把手教学 MVP  模式 (一)
吹爆!阿里大牛MySQL优化笔记有多强?才在GitHub被BATJ联手封杀
SQL注入攻击简介
微服务架构实战学习(七):Eureka 高可使用
浅出https的加密过程
首页
搜索
订单
购物车
我的