Java集合框架03:Collection集合之Set

Set子接口

无序、无下标、元素不可重复

所有方法都继承自Collection父接口,没有自己的方法

HashSet实现类

集合中的元素都是引用类型,都有自己的HashCode,基于HashCode比较可以实现元素不重复,元素默认的存储顺序也是根据这个计算的

存储结构:哈希表(数组+链表或数组+红黑树)

  • 先根据HashCode计算保存的位置,如果该位置为空则直接保存,否则进行下一步比较
  • 再执行equals()方法,如果二者相等则认为重复,否则在该位置形成链表保存
  • 两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能说明这两个对象在一个散列存储结构中。所以自定义规则时必须同时重写hashCode()和equals()方法才能保证元素的唯一性
import java.util.HashSet;
import java.util.Iterator;

public class Hello{
    public static void main(String[] args) {
        //Set<String> set = new HashSet<>();也可以,不常用
        HashSet<String> set = new HashSet<>();
        set.add("小米");
        set.add("苹果");
        set.add("华为");
        set.add("华为");
        
        //添加了重复元素无效,并且顺序和添加的不一样
        System.out.println(set);
        
        //增强for循环遍历
        for (String i : set){
            System.out.println(i);
        }
        
        //iterator()迭代器方法遍历
        Iterator<String> i = set.iterator();
        while (i.hasNext()){
            System.out.println(i.next());
        }
    }
}

拓展:可以同时重写hashCode()和equals()方法,更改规则,使得内容相同的元素就算重复

import java.util.HashSet;

public class Hello{
    public static void main(String[] args) {
        HashSet<Test> set = new HashSet<>();
        Test a = new Test("ty", 25);
        Test b = new Test("tao", 26);
        set.add(a);
        set.add(b);
        
        //重写hashCode()和equals()方法后,元素内容相同就算重复
        set.add(new Test("ty", 25));
        
        System.out.println(set);
    }
}

class Test {
    String name;
    int age;

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

    //重写toString()方法,自定义打印内容
    @Override
    public String toString() {
        return name + " : " + age;
    }

    //重写hashCode()方法,使得内容相同的对象hashCode就相等
    @Override
    public int hashCode() {
        int n1 = name.hashCode();
        int n2 = age;
        return n1 + n2;
    }

    //必须同时重写equals()方法
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (obj instanceof Test) {
            Test ob = (Test) obj;
            //注意比较字符串的内容是否相等,用equals()方法而不是==
            if (this.name.equals(ob.name) && this.age == ob.age){
                return true;
            }
        }
        return false;
    }
}

拓展:哈希表计算hashCode时为什么用31?

  • 31是一个质数,计算时可以减少散列冲突,尽可能生成不同的hashCode
  • 31 * i = (i<<5) - i,31用来计算可以转换为位运算,提高执行效率

TreeSet实现类

基于字典顺序实现元素不重复,默认实现了SortedSet接口,对集合元素自动排序,默认升序

存储结构:红黑树

注意:有多个属性的元素是无法自动排序的,必须自定义排序的顺序,有两种方法,自然排序和比较器排序

  • 自然排序(对象类实现Comparable接口)

元素对象类实现Comparable接口,并重写CompareTo方法指定排列顺序

import java.util.TreeSet;

public class Hello{
    public static void main(String[] args) {
        TreeSet<Test> tree = new TreeSet<>();
        Test a = new Test("ty", 25);
        Test b = new Test("tao", 26);
        Test c = new Test("tao", 27);
		
	//从源码分析,每次调用add()方法时,都会自动调用compareTo()方法将该元素和其他所有元素进行比较
        tree.add(a);
        tree.add(b);
        tree.add(c);

        //此处可以删除原有的元素,因为Test类重写了compareTo()方法,只比较对象的属性,类似于重写了equals()方法
        tree.remove(new Test("ty", 25));
        System.out.println(tree);
    }
}

//1.自然排序,对象类实现Comparable接口,泛型为该对象类型,必须重写其compareTo()方法
class Test implements Comparable<Test>{
    String name;
    int age;

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

    @Override
    public String toString() {
        return name + " : " + age;
    }

    //必须重写接口的compareTo()方法
    @Override
    public int compareTo(Test o) {

        //下面的compareTo()是String类的方法,和上面那个不是同一个
        //按照字典顺序比较,对象字符串在参数字符串前返回负数,在后返回正数,相同返回0
        int n1 = this.name.compareTo(o.name);
        int n2 = this.age - o.age;

        //如果n1 == 0,说明姓名相同,只用比较年龄
        //正数表示放在红黑树的右边,即顺序输出;负数表示放在红黑树的左边,即逆序输出;0表示元素相同,仅存放第一个元素
        //默认是升序,如果要降序,可以加个负号(return n1 == 0 ? -n2 : -n1;)
        return n1 == 0 ? n2 : n1;
    }
}
  • 比较器排序(TreeSet类在构造方法的参数中实现Comparator接口)

在创建集合时自定义规则,TreeSet类选择有参构造,在参数中实现一个匿名的Comparator接口类,并重写compare()方法指定排列顺序,如果返回值为0则为重复元素

import java.util.Comparator;
import java.util.TreeSet;

public class Hello{
    public static void main(String[] args) {
        //2.比较器排序,在构造方法的参数中实现一个匿名接口类Comparator,并重写其compare()方法
        //比较器可用于Collections.sort()和Arrays.sort()方法,但元素必须是引用类型
        TreeSet<Test> tree = new TreeSet<>(new Comparator<Test>() {

            //必须重写接口的compare()方法
            @Override
            public int compare(Test o1, Test o2) {
                int n1 = o1.name.compareTo(o2.name);
                int n2 = o1.age - o2.age;
                return n1 == 0 ? n2 : n1;
            }
        });

//        //2.比较器排序,也可以创建一个成员内部类实现Comparator接口
//        class TestComparator implements Comparator<Test>{
//
//            @Override
//            public int compare(Test o1, Test o2) {
//                int n1 = o1.name.compareTo(o2.name);
//                int n2 = o1.age - o2.age;
//                return n1 == 0 ? n2 : n1;
//            }
//        }
//
//        TreeSet<Test> tree = new TreeSet<>(new TestComparator());

        Test a = new Test("ty", 25);
        Test b = new Test("tao", 26);
        Test c = new Test("tao", 26);
        tree.add(a);
        tree.add(b);
        tree.add(c);

        //此处可以删除原有的元素,因为重写了compare()方法,只比较对象的属性,类似于重写了equals()方法
        tree.remove(new Test("ty", 25));
        System.out.println(tree);
    }
}

class Test {
    String name;
    int age;

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

    @Override
    public String toString() {
        return name + " : " + age;
    }
}

练习:将字符串按照长度排序

import java.util.Comparator;
import java.util.TreeSet;

public class Hello{
    public static void main(String[] args) {
        TreeSet tree = new TreeSet<>(new Comparator<String>() {
            
            //重写compare()方法,先判断长度,再判断字典顺序
            @Override
            public int compare(String o1, String o2) {
                int n1 = o1.length()-o2.length();
                int n2 = o1.compareTo(o2);
                return n1 == 0 ? n2 : n1;
            }
        });

        tree.add("beijing");
        tree.add("tianjin");
        tree.add("xian");
        tree.add("jinan");
        System.out.println(tree);
    }
}
posted @ 2021-09-11 16:22  振袖秋枫问红叶  阅读(87)  评论(0)    收藏  举报