zoukankan      html  css  js  c++  java
  • Java集合之Set

    正文

    Set的特点?

    • 它与List(关于List可以参考Java集合之List)不同,它是一个不包含重复元素的collection(Set所具有的方法和Collection所具有的方法一致)。

    • 它是无序的,但当向集合中存储元素的顺序与Set内部存储元素的顺序一致时就可能会出现有序的情况,只不过这种情况出现的概率很小。

    Set的常用子类?

    关于这一部分我们的讲解思路与List相同:先明确各自的特点,知道具体的场景该用哪一种;再分析各自所具有的一些方法。这两者都可以通过查看jdk文档获悉。

    HashSet?

    HashSet内部的数据结构是哈希表;它是不同步的。

    那么哈希表是什么呢?顾名思义,它就是一张使用哈希算法得出的表。通过哈希算法,我们存取元素的过程就变成了这样:假如我们要存储元素A,先根据哈希算法算出A元素的哈希值,再根据该哈希值得出元素A存储到集合中的位置,根据该位置存储即可;假如我们要从集合中取出元素B,还是先根据哈希算法算出B元素的哈希值,再根据该哈希值到集合中指定的位置查找,如果该位置上的元素是B,就直接取出,如果不是,就说明该集合中没有B元素。

    由于哈希表的这种特点,所以哈希表中的元素是不能重复的。哈希表确定两个元素是否相同的依据是:先用hashCode()算出两个元素的哈希值,如果二者相等再使用equals()判断两者的内容,如果两者内容也相同,那么哈希表就认为这两个元素是相同的。

    由于Object类中有hashCode(),所以Java中的所有对象都有哈希值。并且Object类中的hashCode()被native修饰是底层实现的。但是我们可以自定义该方法的实现。

    当两个不同的元素算出的哈希值却相等时,就出现了哈希冲突。这种情况下如何进行存取元素就会视具体的哈希算法而定。

    当向HashSet中存储自定义对象并且我们对这些对象是否相等有自己的逻辑时,就可以在对象所属的类中覆盖hashCode()和equals()。假设现在有这样的需求:向HashSet集合中存储Person对象并且如果姓名和年龄相同就视为同一个人。代码如下:

    
    import java.util.HashSet;
    import java.util.Iterator;
    
    class Person {
        private String name;
        private int age;
    
        public Person() {
            super();
    
        }
        public Person(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
    
        /**
         * 覆盖hashCode()和equals()实现自己的判断逻辑
         * @return
         */
        @Override
        public int hashCode() {
            return name.hashCode() + age * 27;
        }
        @Override
        public boolean equals(Object obj) {
            if(this == obj)
                return true;
            if(!(obj instanceof Person))
                throw new ClassCastException("类型错误");
    
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.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;
        }
        public String toString(){
            return name+":"+age;
        }
    }
    
    class Demo {
        public static void main(String[] args) {
    
            HashSet hs = new HashSet();
            hs.add(new Person("lisi4",24));
            hs.add(new Person("lisi7",27));
            hs.add(new Person("lisi1",21));
            hs.add(new Person("lisi7",27));
    
    
            Iterator it = hs.iterator();
            while(it.hasNext()){
                Person p = (Person)it.next();
                System.out.println(p);
            }
        }
    }
    
    

    我们要注意List接口中的contains()和remove()内部使用的都是equals()进行判断,即是:如果我们需要使用contains()或equals()实现自定义判断逻辑,只需覆盖equals()即可。假设现在有这样的需求:删除ArrayList中重复的Person对象并且当Person对象的姓名和年龄相同就视为同一个人。代码如下:

    
    import java.util.ArrayList;
    import java.util.Iterator;
    
    class Person {
        // 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
    
        @Override
        public boolean equals(Object obj) {    // 仅覆盖equals()即可
            if(this == obj)
                return true;
            if(!(obj instanceof Person))
                throw new ClassCastException("类型错误");
    
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age;
        }
    }
    
    class Demo {
        public static void main(String[] args) {
    
            ArrayList al = new ArrayList();
            al.add(new Person("lisi1", 21));
            al.add(new Person("lisi2", 22));   // 1
            al.add(new Person("lisi3", 23));
            al.add(new Person("lisi4", 24));
            al.add(new Person("lisi2", 22));
            al.add(new Person("lisi3", 23));
    
            al = getSingleElement(al);
            System.out.println(al);
    
            System.out.println(al.remove(new Person("lisi2",22)));   // 与1处的对象是两个对象,但由于我们覆盖了equals()并实现了自己的判断逻辑,所以返回true并且删除成功
    	System.out.println(al);
        }
    
        private static ArrayList getSingleElement(ArrayList al) {
            ArrayList temp = new ArrayList();
    
            Iterator it = al.iterator();
    
            while(it.hasNext()){
                Object obj = it.next();
    
                if(!temp.contains(obj)){
                    temp.add(obj);
                }
            }
            return temp;
        }
    }
    
    

    所以我们可以总结:如果涉及到判断两个对象是否相等,最好hashCode()和equals()都进行覆盖,这样对List和HashSet都适用。

    如果我们希望集合在唯一时还能保证有序性,我们就可以使用LinkedHashSet。它内部是由链表和哈希表共同实现的。

    TreeSet?

    TreeSet的内部的数据结构是二叉树;它使用元素的自然顺序对元素进行排序;它是不同步的。

    TreeSet判断元素是否唯一的根据是:判断比较方法的返回结果,如果是0,就说明是同一个元素。

    我们需要注意:TreeSet对元素进行排序有两种方式,第一种就是让需要排序的类实现Comparable接口并且覆盖compareTo()。假设现在有这样的需求:以Person对象的年龄进行从小到大的排序。代码如下:

    
    // 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
    
    import java.util.Iterator;
    import java.util.TreeSet;
    
    class Person implements Comparable {   // 实现Comparable接口 
        @Override
        public boolean equals(Object obj) {    // 仅覆盖equals()即可
            if(this == obj)
                return true;
            if(!(obj instanceof Person))
                throw new ClassCastException("类型错误");
    
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age;
        }
    
        @Override  
        public int compareTo(Object o) {   // 覆盖compareTo()并实现自己的判断逻辑
            Person p = (Person)o;
    
            int  temp = this.age - p.age;
            return temp == 0 ? this.name.compareTo(p.name) : temp;
        }
    }
    
    class Demo {
        public static void main(String[] args) {
    
            TreeSet ts = new TreeSet();
    
            ts.add(new Person("zhangsan",28));
            ts.add(new Person("lisi",21));
            ts.add(new Person("zhouqi",29));
            ts.add(new Person("zhaoliu",25));
            ts.add(new Person("wangu",24));
    
            Iterator it = ts.iterator();
    
            while(it.hasNext()){
                Person p = (Person)it.next();
    
                System.out.println(p.getName() + ":" + p.getAge());
            }
        }
    }
    
    

    有很多类都实现了Comparable接口,意味着这些类都自定义了排序方式,这种排序方式被称为类的自然排序,那么当我们不想要按照这种自然排序方式进行排序时,就可以使用第二种方法:定义一个类(比较器)让其实现Comparator接口并且覆盖compare(),并将该类对象作为参数传递给TreeSet集合的构造函数。代码如下:

    
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.TreeSet;
    
    class Person {
    // 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
    
        @Override
        public int hashCode() {
            return name.hashCode() + age * 27;
        }
        @Override
        public boolean equals(Object obj) {
            if(this == obj)
                return true;
            if(!(obj instanceof Person))
                throw new ClassCastException("类型错误");
    
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age;
        }
    }
    
    /**
     * 一个根据Person类的name进行排序的比较器
     */
    class ComparatorByName implements Comparator {
    
        @Override
        public int compare(Object o1, Object o2) {
    
            Person p1 = (Person)o1;
            Person p2 = (Person)o2;
    
            int temp = p1.getName().compareTo(p2.getName());
    
            return temp == 0 ? p1.getAge() - p2.getAge() : temp;
        }
    
        public static void main(String[] args) {
            TreeSet ts = new TreeSet(new ComparatorByName());
    
    
            ts.add(new Person("zhangsan",28));
            ts.add(new Person("lisi",21));
            ts.add(new Person("zhouqi",29));
            ts.add(new Person("zhaoliu",25));
            ts.add(new Person("wangu",24));
    
            Iterator it = ts.iterator();
    
            while(it.hasNext()){
                Person p = (Person)it.next();
    
                System.out.println(p.getName() + ":" + p.getAge());
            }
        }
    }
    
    

    由于HashSet可以实现任意方式的排序,那我们也可以实现HashSet的有序,代码见下:

    
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.TreeSet;
    
    class Person {
    // 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
    
        @Override
        public int hashCode() {
            return name.hashCode() + age * 27;
        }
        @Override
        public boolean equals(Object obj) {
            if(this == obj)
                return true;
            if(!(obj instanceof Person))
                throw new ClassCastException("类型错误");
    
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age;
        }
    }
    
    /**
     * 一个实现HashSet有序的比较器
     */
    class ComparatorByName implements Comparator {
    
        @Override
        public int compare(Object o1, Object o2) {
    
            Person p1 = (Person)o1;
            Person p2 = (Person)o2;
    
            return 1; 
        }
    
        public static void main(String[] args) {
            TreeSet ts = new TreeSet(new ComparatorByName());
    
    
            ts.add(new Person("zhangsan",28));
            ts.add(new Person("lisi",21));
            ts.add(new Person("zhouqi",29));
            ts.add(new Person("zhaoliu",25));
            ts.add(new Person("wangu",24));
    
            Iterator it = ts.iterator();
    
            while(it.hasNext()){
                Person p = (Person)it.next();
    
                System.out.println(p.getName() + ":" + p.getAge());
            }
        }
    }
    
    
  • 相关阅读:
    CPPFormatLibary提升效率的优化原理
    Unity4、Unity5移动平台多线程渲染在部分安卓手机上会造成闪退
    Hello World!
    Mac/IOS/linux获取当前时间包含微秒毫秒的代码
    插入图块闪烁问题的原因
    选择实体时的选项
    dataGridView 单元格添加combox checkbox
    交互拾取点时 右键取消 禁止出现点无效
    曲线上到指定点最近的点
    移除实体应用程序名(xdata)
  • 原文地址:https://www.cnblogs.com/syhyfh/p/12531797.html
Copyright © 2011-2022 走看看