集合框架之Set

集合框架Collection的分支情况
这里写图片描述
本篇博客总结右半部分集合框架–Set
回顾List,List存放的元素是有序的,可重复的。
1:LinkedList的插入删除效率较高;
2:ArrayList的查询效率较高。
区别集合框架Set与List的最大的地方就是前者要求集合内的元素不得重复!!!
下面我们来看集合框架中的两个主要的部分HashSet和TreeSet

一:HashSet

重点总结:
1:是否允许重复上:HashSet要求集合内部不允许出现重复的元素,而且由于HashSet是以哈希算法为基础实现的,所以他不保证迭代的顺序是恒久不变的。
2:遍历元素的方式上:和List中不同,在实现Set接口的集合类中是无法通过下标来遍历到集合中的元素的,所以必须通过iterator(迭代器)来进行遍历元素。
3:判断是否有重复元素的方式上:在ArrayList中要通过将集合内所有的元素进行遍历的方式来去对比判断是否有重复的元素。而HashSet是通过特定的hashCode()函数方法来计算出哈希值并且进行判断是否有重复出现。(HashSet判断的不是是否产生新对象,而是哈希值的异同)

注意:一般来讲使用HashSet进行存储元素的时候,他在读进去元素的时候,将会使用的两个方法,一个是hashCode()方法,另一个是equals()方法。一般来讲当两个对象不同的时候,他的哈希值是不同的所以用不到equals方法,但是当出现特殊情况的时候,由于自己定义的哈希算法的弊端出现,可能会导致,对象不同而哈希值相同,此时就要使用到equals()方法了。下面将会详细说明使用HashSet来添加元素的原理。
这里写图片描述
由上图可以看出字符串”ab”和字符串”ba”他们的哈希值是一样的,但是这两个字符串明显是不一样的,他们还能不能插入进集合中呢?答案是肯定可以的!此时就需要用到方法equals()了。在方法equals中你需要将字符串中的每一个字符都拿出来逐一的进行比较,在下面的实例中会详细的说明这两个方法的使用。
实例一:
要求:将几个Person对象添加进HashCode集合中。

//Test类  1:执行插入对象操作。2:执行遍历输出集合中的元素操作。
public class Test {
    public static void main(String[] args) {
        HashSet hs = new HashSet();
        hs.add(new Person("张三",18));//**
        hs.add(new Person("李四",12));
        hs.add(new Person("王五",11));
        hs.add(new Person("张三",18));//此处有重复
        hs.add(new Person("张三",15));
        for (Iterator it = hs.iterator();it.hasNext(); ) {
            Person p = (Person) it.next();
            System.out.println(p+"------"+p.hashCode());
        }
    }
}
//Person类(实体类)1:重写toString方法;2:重写hashCode方法;3重写equals方法
public class Person {
    String name;
    int age;
    //构造方法
    public Person (String name, int age) {
        this.name = name;
        this.age = age;
    }
    //重写方法(默认情况下输出的是对象的存储位置)
    public String toString() {
        return name +"===="+age;
    }
    //重写方法,在执行HashSet对象执行add方法时自动调用
    public int hashCode() {
        return name.hashCode()+age;
    }
    //重写方法,hashcode相同那么会再调用equals方法进行判断是否相同
    public boolean equals(Object arg0) {
        Person p = (Person) arg0;
        return p.name.equals(name) && age==p.age;
    }
}

运行结果:
这里写图片描述
分析:从运行结果中可以看出重复的那个对象被自动抛弃了,而且所有可存储的对象都按照hashCode方法计算出的哈希值进行特定的顺序排序。
我们来看为什么要在实体类中重写这三个方法,首先这三个方法是超类Object的子类,他们都是超类中的方法(都是对象的方法)。所以需要在实体类Person中进行重写。
1:toString方法:此方法在默认情况下返回的是对象的地址信息,无法达到要求所以要求重写方法。
2:hashCode方法:当默认情况下的hashCode方法无法满足既定的要求的时候,此时重写一个hashCode方法,返回值是name.hashCode()+age====注意:name是一个String类型的数据,也是一个对象,所以可以直接调用hashCode方法返回哈希值,这是一个组合哈希值,为的是防止 姓名相同而年龄不同的不同的对象无法插入进HashCode集合中。
3:equals方法:当哈希值相同的时候,为了防止弊端出现,就必须要调用equals方法来进行进一步的判断。 equals(Object arg0),括号中的是已经插入进去的数据,正在add的对象要调用这个方法来进行判断。返回值是正确或者错误。将导致add操作成功或者失败。

二:TreeSet

TreeSet是一个有序的集合框架。他使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序。
TreeSet的数据结构是二叉树,他的实现原理如下图所示(从头挨个比较):
这里写图片描述
在TreeSet中必须要注意的是:
在TreeSet内部的元素必须要实现有序的存放,但是实现这种有序存放的方法不是默认存在的,所以在创建TreeSet的时候必须要实现元素比较的方法!!!!
比较方法一:
通过实现comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
1:在对象实体类中实现comparable接口 (若有特殊需求就要将comparaTo方法重写一次)
2:在test中这个方法是在add的时候由集合框架对象自动调用的。
比较方法二:
创建 set 时提供的 Comparator(比较器) 进行排序,具体取决于使用的构造方法(构造一个新的空 TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器中定义的比较方法进行相互比较)
1:创建一个comparator(比较器)类,并实现(implements) comparator接口
2:在Test中创建TreeSet的时候将比较器扔进TreeSet中【如:TreeSet ts = new TreeSet(new StudentComparator( ) ); 】。
3:此时在进行add的时候,他的比较将自动执行重写的比较器中的方法。
比较方法一实例:

//测试类Test    1:new出新对象并且进行add操作;2:使用迭代器进行遍历输出验证。
public class Test {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
        ts.add(new Student("张三", 12));
        ts.add(new Student("李三", 13));
        ts.add(new Student("李四", 11));
        ts.add(new Student("王五", 16));
        ts.add(new Student("李三", 10));
        for (Iterator it = ts.iterator();it.hasNext();) {
            Student stu = (Student) it.next();
            System.out.println(stu);
        }
    }
}
//实体类Student   1:实现comparable接口;2:写构造函数;3:重写toString方法;4:重写compareTo方法
public class Student implements Comparable{
    String name;
    int age;

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

    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

    //重写方法一,根据age来进行排序
    public int compareTo(Object arg0) {
        Student stu = (Student) arg0;
        return stu.age-age;
        //age 是准备存储的那个数,还未存储!stu.age是要比较的数字(已经存储进去了)
    }
    /*
    //重写方法二:根据name字符串来进行排序
    public int compareTo(Object arg0) {
        Student stu = (Student) arg0;
        return name.compareTo(stu.name);
        /*name是一个String类型的数据,String类型的数据早已经实现implements了comparable接口,所以,可以直接调用comparaTo(),字符串存储的时候是划分成字符,放在数组里面进行存储的,所以在进行比较的时候,他是每一位每一位的进行比较。如:“aaa”=====保存方式是[a,a,a]*/
        */
    }
}

运行结果如下
这里写图片描述
分析:如图,可以看出以age来进行排序的,实现步骤在前面介绍方法的时候已经详细说明了。注意方法compareTo返回的是一个int类型的数据;这个方法的主要目的在于比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。如果返回的是0,那么说明这两个对象是相同的,就会产生对象覆盖的现象。

比较方法二实例:

//测试类Test   1:新建比较器放入TreeSet;2:输出集合进行验证
public class Test {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new StudentComparator());
            ts.add(new Student("张三", 12));
            ts.add(new Student("李三", 13));
            ts.add(new Student("李四", 11));
            ts.add(new Student("王五", 16));
            ts.add(new Student("李三", 10));
        System.out.println(ts);//输出集合ts
    }
}
//创建一个比较器类,1:实现接口Comparator:;2:重写方法compare。
import java.util.Comparator;
public class StudentComparator implements Comparator{
    public int compare(Object arg0, Object arg1) {
        Student s1 = (Student) arg0; 
        Student s2 = (Student) arg1; 
        return s1.age-s2.age;
    }
}
//实体类Student,1:写构造函数,2:重写toString方法
public class Student {
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}

结果如下:
这里写图片描述
分析:第二种方法的实现步骤已经在前边详细介绍。要区分好这两个方法。

总结:
Set接口两个实现类分别为HashSet和TreeSet,实现TreeSet的时候一定要记住将两个比较方法写出来一个!具体使用什么方法,看需求。多加练习,多加总结。

猜你喜欢

转载自blog.csdn.net/sun_DongLiang/article/details/81072400