Collection子接口:Set接口

Set接口概述:

1)Set接口是Collection的子接口,set接口没有提供额外的方法。

2)Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。

3)Set判断两个对象是否相同不是==运算符,而是根据equals()方法。

Set实现类:HashSet

1)HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。

2)HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。

3)HashSet具有以下特点:不能保证元素的排列顺序,HashSet不是线程安全,集合元素可以是null。

4)HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。

5)对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

Set实现类:LinkedHashSet

1)LinkedHashSet是HashSet的子类。

2)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

3)LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。

4)LinkedHashSet不允许集合元素重复。

Set实现类:TreeSet

1)TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素出于排序状态。

2)TreeSet底层使用红黑树结构存储数据。

3)新增的方法如下:Comparator comparator()、Object first()、Object last()、Object lower(Object e)、Object higher(Object e)、SortedSet subSet(fromElement,toElement)、SortedSet headSet(toElement)、SortedSet tailSet(fromElement)

4)TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。

Set接口的框架:

Collection接口:单列集合,用来存储一个一个的对象

        Set接口:存储无序的、不可重复的数据据 --->高中讲的“集合”

                HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值。

                       LinkedHashSet:作为HashSet的子类;遍历内部数据时,可以按照添加顺序遍历。

                                                     对于频繁的遍历的操作,LinkedHashSet效率高于HashSet。

                TreeSet:可以按照添加对象的指定属性,进行排序。

1)Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。

2)要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()。

                重写的hashCode()和equals()尽可能保持一致性。

Set的无序性和不可重复性:

以HashSet为例说明:

1)无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据哈希值值决定

2)不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个。

添加元素的过程:

以HashSet为例说明:

        我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的的哈希值,此哈希值接着通过某种算法计算处在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:

1)如果此位置上没有其他元素,则元素a添加成功。

2)如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:

        a)如果hash值不相同,则元素a添加成功;

        b)如果hash值相同,进而需要调用元素a所在类的equals()方法:

                equals()返回true,元素a添加失败;

                equals()返回false,则元素a添加成功。

对于添加成功的情况而言:元素a与已经存在指定索引位置上数据以链表的方式存储。

jdk7:元素a放到数组中,指向原来的元素。

jdk8:原来的元素在数组中,指向元素a

总结:七上八下

HashSet底层:数组+链表的结构

向HashSet中添加元素的过程:

1)当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值,通过某种散列函数决定该2对象在HashSet底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下表,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)

2)如果两个元素的hashCode()值相等,会再继续调用equals方法,如果过equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。

3)如果两个元素的equals()方法返回true,但它们的hashCode()返回值不相等,hashSet将会把他们存储再不同的位置,但依然可以添加成功。

Eclipse/IDEA工具里hashCode()重写:

以Eclipse/IDEA为例,在自定义中可以调用工具重写equals和hashCode。问题:为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

1)选择系数的时候要选择尽量大的系数,因为如果计算出来的hash地址越大,所谓的“冲突“就越少,查找起来效率也会越高。(减少冲突)

2)并且31只占用5bits,相乘造成数据溢出的概率较小。

3)可以由i*31==(i<<5)-1来表示,现在很多虚拟机里面都有做相关的优化。(提高算法效率)

4)31是一个素数,素数作用就是如果用一个数字来乘以这个素数,那么最终出来的结果只能被素数,那么最终出来的结果只能被素数本身和被素数本身和被乘数还有1来整数。(减少冲突)

重写equals()方法的基本原则:

以自定义的Customer类为例,何时需要重写equals()?

1)当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同不同的实例有可能在逻辑上是相等的,但是根据Object.hashCode()方法,他们仅仅是两个对象。

2)因此,违反了“相等的对现象必须具有相等的散列码”。

3)结论:复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行性计算。

重写hashCode()方法的基本原则:

1)在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。

2)当两个对象的equals()方法比较返回true时,这连哥哥对象的hashCode()方法的返回值也应相等。

3)对象中用作equals()方法比较Field,都应该用来计算hashCode值。

重写两个方法的小技巧:对象中用作equals()放大比较的Field,都应该用来计算hashCode值。

import org.junit.Test;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetTest {
    @Test
    public void test1(){
        Set set=new HashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom",12));
        set.add(new User("Tom",12));
        set.add(129);

        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
            //未重写hashCode时User不算重复
            /*输出:AA
                    CC
                    129
                    456
                    123
            */
        }
    }
}
public class User {
    private String name;
    private int age;

    public User(){
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    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;
    }
}

LinkedHashSet的使用:

        LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历的操作,LinkedHashSet效率高于HashSet

    @Test
    public void test2(){
        Set set=new LinkedHashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom",12));
        set.add(new User("Tom",12));
        set.add(129);

        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
            //未重写hashCode时User不算重复
            /*
                456
                123
                AA
                CC
                User{name='Tom', age=12}
                129
            */
        }
    }
}

TreeSet的使用:

1)向TreeSet中添加的数据,要求是由相同类的对象。

2)两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)。

                   自然排序中,比较两个对象是否相同的标准为compareTo()返回0,不再是equals()。

                   定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()

3)TreeSet和TreeMap采用红黑树的存储结构。

特点:有序,查询速度比List快。

//自然排序
    @Test
    public void test1(){
        TreeSet set=new TreeSet();

        //失败:不能添加不同类的对象:
        //set.add(123);
        //set.add(456);
        //set.add("AA");
        //set.add(new User("Tom",12));

        //举例一
        //set.add(34);
        //set.add(-34);
        //set.add(43);
        //set.add(11);
        //set.add(8);
        //-34、8、11、34、43

        //举例二
        set.add(new User("Tom",12));
        set.add(new User("Jerry",12));
        set.add(new User("Jim",12));
        set.add(new User("Mike",12));
        set.add(new User("Jack",12));
        set.add(new User("Jack",21));

        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
            //输出:User{name='Jack', age=12}
            //User{name='Jerry', age=12}
            //User{name='Jim', age=12}
            //User{name='Mike', age=12}
            //User{name='Tom', age=12}
            // User{name='Jack', age=21}
        }
    }
//定制排序
    @Test
    public void test2(){
        Comparator com=new Comparator() {
            //按照年龄从小到大排序
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1=(User) o1;
                    User u2=(User) o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else{
                    throw new RuntimeException("输入的数据类型不匹配");
                }
            }
        };
        TreeSet set=new TreeSet(com);
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",62));
        set.add(new User("Jack",33));
        set.add(new User("Merry",33));
        set.add(new User("Jack",21));

        Iterator iterator=set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
            //User{name='Jim', age=2}
            //User{name='Tom', age=12}
            //User{name='Jack', age=21}
            //User{name='Jerry', age=32}
            //User{name='Jack', age=33}
            //User{name='Mike', age=62}
        }
    }

public class User implements Comparable{
    private String name;
    private int 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 User(){
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    //按照姓名从大到小排序,年龄从小到大排序
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user=(User)o;
            //return this.name.compareTo(user.name);从小到大
            //return -this.name.compareTo(user.name); 从大到小
            int compare=-this.name.compareTo(user.name);
            if(compare!=0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不匹配");
        }
    }
}

练习题:

eg1:定义一个Employee类包含:private成员变量name,age,bitthday,其中birthday为MyDate类的对象:并为每一个属性定义getter,setter方法;并重写toString方法输出name,age,birthday。

MyDate类包含:private成员变量year,month,day;并为每一个属性定义getter,setter方法;

创建该类的5个对象,并把这些对象放入TreeSet集合中,分别按以下两种方式对集合元素进行排序,并遍历输出:

1)使Employee实现Comparable接口,并按name排序

2)创建TreeSet时传入Comparator对象,按生日日期的先后排序

public class MyDate implements Comparable{
    private int year;
    private int month;
    private int day;

    public MyDate() {
    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        if(o instanceof MyDate){
            MyDate m=(MyDate) o;

        //比较年
        int minusYear=this.getYear()-m.getYear();
        if(minusYear!=0){
            return minusYear;
        }

        //比较月
        int minusMonth=this.getMonth()-m.getMonth();
        if(minusMonth!=0){
            return minusMonth;
        }

        //比较日
        return this.getDay()-m.getDay();
        }
        throw new RuntimeException("传入的数据类型不一致!");
    }
}
public class Employee implements Comparable{
    private String name;
    private int age;
    private MyDate birthday;

    public Employee() {
    }

    public Employee(String name, int age, MyDate birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    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 MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        if(o instanceof Employee){
            Employee e=(Employee) o;
            return this.name.compareTo(e.name);
        }
        //return 0;
        throw new RuntimeException("传入的数据类型不一致!");
    }
}
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class EmployeeTest {
    //问题一:使用自然排序
    @Test
    public void test1(){

        TreeSet set=new TreeSet();
        Employee e1=new Employee("刘德华",55,new MyDate(1965,5,4));
        Employee e2=new Employee("张学友",55,new MyDate(1987,5,4));
        Employee e3=new Employee("郭富城",55,new MyDate(1987,5,9));
        Employee e4=new Employee("黎明",55,new MyDate(1954,8,12));
        Employee e5=new Employee("梁朝伟",55,new MyDate(1978,12,4));

        set.add(e1);
        set.add(e2);
        set.add(e3);
        set.add(e4);
        set.add(e5);

        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            System.out.println();
        }
    }

    //问题二:按生日日期的先后排序
    @Test
    public void test2(){
        TreeSet set=new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof Employee && o2 instanceof Employee){
                    Employee e1=(Employee) o1;
                    Employee e2=(Employee) o2;

                    MyDate b1=e1.getBirthday();
                    MyDate b2=e2.getBirthday();

                    //方式一:
                    //比较年
                    //int minusYear=b1.getYear()-b2.getYear();
                    //if(minusYear!=0){
                    //    return minusYear;
                    //}

                    //比较月
                    //int minusMonth=b1.getMonth()-b2.getMonth();
                    //if(minusMonth!=0){
                    //    return minusMonth;
                    //}

                    //比较日
                    //return b1.getDay()-b2.getDay();

                    //方式二
                    return b1.compareTo(b2);
                }
                //return 0;
                throw new RuntimeException("传入的数据类型不一致!");
            }
        });

        Employee e1=new Employee("刘德华",55,new MyDate(1965,5,4));
        Employee e2=new Employee("张学友",55,new MyDate(1987,5,4));
        Employee e3=new Employee("郭富城",55,new MyDate(1987,5,9));
        Employee e4=new Employee("黎明",55,new MyDate(1954,8,12));
        Employee e5=new Employee("梁朝伟",55,new MyDate(1978,12,4));

        set.add(e1);
        set.add(e2);
        set.add(e3);
        set.add(e4);
        set.add(e5);

        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            System.out.println();
        }
    }
}

eg2:在List内去除重复数字值,要求尽量简单。

import java.util.List;

public static List duplicateList(List list){
    HashSet set=new HashSet();//不重复
    set.addAll(list);
    return new ArrayList(set);
}

public static void main(String[] args){
        List list=new ArrayList();
        list.add(new Integer(1));
        list.add(new Integer(2));
        list.add(new Integer(2));
        list.add(new Integer(4));    
        list.add(new Integer(4));
        
        List list2=duplicateList(list);
        for(Object integer:list2){
            System.out.println(integer);//1、2、4
        }
}

eg3:考虑输出的问题,其中Person类中重写了hashCode()和equal()方法。

public class Person {
    private int id;
    public String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Person() {
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}
public class MyTest {
    @Test
    public void test3(){
        HashSet set=new HashSet();
        Person p1=new Person(1001,"AA");
        Person p2=new Person(1002,"BB");

        set.add(p1);
        set.add(p2);

        p1.name="CC";
        set.remove(p1);
        System.out.println(set);
        //[Person{id=1002,name='BB'},Person{id=1001,name='CC'}]


        set.add(new Person(1001,"CC"));
        System.out.println(set);
        //[Person{id=1002,name='BB'},Person{id=1001,name='CC'},Person{id=1001,name='CC'}]

        set.add(new Person(1001,"AA"));
        System.out.println(set);
        //[Person{id=1002,name='BB'},Person{id=1001,name='CC'},Person{id=1001,name='CC'},Person{id=1001,name='AA'}]
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值