Java学习笔记——Set集合及其子类

对Set集合的阐述

Set集合类似于一个罐子,我们可以依次把多个对象丢入Set集合,但是Set集合通常不记住元素的添加顺序

Set集合与Colletcion基本相同,只不过Set集合不允许出现相同的元素,如果使用add方法添加是出现相同元素,则会返回false值并且该相同元素不会被加入。

Set集合就只有这些东西,我们主要讲的还是Set集合的三个实现类:HashSet、LinkedHashSet、TreSet。

一.HashSet类

HashSet类作为Set集合的一个实现类,主要是以Hash算法来存储集合中的元素,因此具有很好的存储以及查询性能。

主要原理是,在向HashSet集合中添加一个元素时,它会调用该对象的hashCode( )方法来得到该对象的哈希值,然后通过该对象的哈希值判断在HashSet集合中的存储位置。在后续添加中,如果两个元素如果equals( )方法比较成功,同时hashCode( )方法返回值也相等,则说明他们是相同内容的东西,因此无法添加成功。

但是如果equal( )方法返回值相等,hashCode( )返回值不相等,则该元素还是会被添加到集合中,这个会非常麻烦,在后面会讲到对于此类的解决方法。

public class HashSetDemo1 {
    public static void main(String[] args) {
        //正常情况
        Set<String> set=new HashSet<String>();
        set.add("Tempestissimo 11.50");
        set.add("Tempestissimo 11.50");
        set.add("Grievous Lady 11.30");
        set.add("Feacture Ray 11.20");
        set.add("Feacture Ray 11.20");
        set.add("SAIKYO STRONGER 11.00");
        set.add("Aegleseeker 11.00");

        for(String a:set){
            System.out.println(a);
        }
    }
}

HashSet类的特点

  1. 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
  2. HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,就必须通过代码来保证其同步。
  3. 集合元素值可以是null

对于使用HashSet集合时判断的标准

在判断时,若没有重写equals( )和hashCode( )方法,则会直接调用Object类中的这两个方法,而这样子做相当于没有比较,因此若我们要重写该对象对应类比较方法时,必须两个方法都要同时重写,即如果重写了equals( )方法,就必须重写hashCode( )方法,反之亦然

如果两个对象的equals( )方法返回的都是true,而hashCode( )方法返回值不相等,则会把这两个相同的对象存储在两个不同的位置。这样子与Set集合的规则 冲突;若如果hashCode( )方法返回的是true但是equals( )方法返回的值是false,则会导致性能下降。

其实很简单,把equals( )方法和hashCode方法( )都重写了就得了

要注意的一点是,在可变对象添加到了HashSet集合之后,不要再去修改集合中参与计算的hashCode( ),equals( )的实例变量,否则会导致HashSet( )无法正确 操作这一些元素。

public class HashSetDemo2 {
    public static void main(String[] args) {
        Set<SongInfo> set=new HashSet<SongInfo>();
        SongInfo song1=new SongInfo("Tempestissimo",11.50f);
        SongInfo song2=new SongInfo("Tempestissimo",11.50f);
        SongInfo song3=new SongInfo("Grievous Lady",11.30f);
        SongInfo song4=new SongInfo("Feacture Ray",11.20f);
        SongInfo song5=new SongInfo("SAIKYO STRONGER",11.00f);
        SongInfo song6=new SongInfo("SAIKYO STRONGER",11.00f);
        SongInfo song7=new SongInfo("Aegleseeker",11.00f);

        set.add(song1);
        set.add(song2);
        set.add(song3);
        set.add(song4);
        set.add(song5);
        set.add(song6);
        set.add(song7);

        for(SongInfo a:set){
            String songname=a.getSongname();
            float rating=a.getRating();
            System.out.println(songname+"-----"+rating);
        }

    }
}

//Songinfo类
public class SongInfo {
    private String songname;
    private float rating;

    public SongInfo() {
    }

    public SongInfo(String songname, float rating) {
        this.songname = songname;
        this.rating = rating;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SongInfo songInfo = (SongInfo) o;
        return Float.compare(songInfo.rating, rating) == 0 &&
                songname.equals(songInfo.songname);
    }

    @Override
    public int hashCode() {
        return Objects.hash(songname, rating);
    }
}
//这里重写了equals方法和hashCode方法

二,LinkedSet类

和HashSet差不多,最主要的是底层数据结构有个链表,因此记录了插入顺序,同时使用Hash算法来维护这个链表

public class LinkedSetDemo {
    public static void main(String[] args) {
        Set<String> set=new LinkedHashSet<String>();
        set.add("Hello");
        set.add("World");
        set.add("World");
        set.add("Java");

        for(String a:set){
            System.out.println(a);
        }
    }
}
输出结果
Hello
World
Java

三.TreeSet类

底层数据结构为红黑树,储存的数据有有序性。其排序有两种方式:自然排序和定制排序

1. 自然排序

自然排序是指利用集合元素的 compareTo( )方法来比较元素之间的大小关系,然后将集合元素按升序排序

注意,实现compareTo方法必须先实现Comparable接口

public class TreeSetDemo1 {
    public static void main(String[] args) {
        Set<SongInfo> set=new TreeSet<SongInfo>();

        SongInfo song1=new SongInfo("Tempestissimo",11.50f);
        SongInfo song2=new SongInfo("Tempestissimo",11.50f);
        SongInfo song3=new SongInfo("Grievous Lady",11.30f);
        SongInfo song4=new SongInfo("Feacture Ray",11.20f);
        SongInfo song5=new SongInfo("SAIKYO STRONGER",11.00f);
        SongInfo song6=new SongInfo("SAIKYO STRONGER",11.00f);
        SongInfo song7=new SongInfo("Aegleseeker",11.00f);

        set.add(song1);
        set.add(song2);
        set.add(song3);
        set.add(song4);
        set.add(song5);
        set.add(song6);
        set.add(song7);

        for(SongInfo a:set){
            String songname=a.getSongname();
            float rating=a.getRating();
            System.out.println(songname+"-----"+rating);
        }

    }
}
//输出结果
Tempestissimo-----11.5
Grievous Lady-----11.3
Feacture Ray-----11.2
SAIKYO STRONGER-----11.0
Aegleseeker-----11.0

SongInfo类

public class SongInfo implements Comparable{
    public String songname;
    public float rating;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SongInfo songInfo = (SongInfo) o;
        return Float.compare(songInfo.rating, rating) == 0 &&
                songname.equals(songInfo.songname);
    }

    @Override
    public int hashCode() {
        return Objects.hash(songname, rating);
    }

    @Override
    //这里重写了compareTo方法
    public int compareTo(Object o) {
        SongInfo a = (SongInfo) o;
        if (a.rating > this.rating) {
            return 1;
        }
        else if(a.rating==this.rating&&a.songname!=this.songname){
            return 1;
        }
        else return 0;
    }
}

2. 比较器排序

如果想实现定制排序可以通过Comparator接口的帮助,提供一个Comparator对象与该TreeSet集合关联,由该对象来负责该集合元素的排序逻辑

例如下面的代码

public class TreeSetDemo {
	public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
			@Override
			public int compare(Student s1, Student s2) {
				int num = s1.getName().length() - s2.getName().length();
				int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
						: num;
				int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
				return num3;
			}
		});

		Student s1 = new Student("linqingxia", 27);
		Student s2 = new Student("zhangguorong", 29);
		Student s3 = new Student("wanglihong", 23);
		Student s4 = new Student("linqingxia", 27);
		Student s5 = new Student("liushishi", 22);
		Student s6 = new Student("wuqilong", 40);
		Student s7 = new Student("fengqingy", 22);
		Student s8 = new Student("linqingxia", 29);

		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		ts.add(s4);
		ts.add(s5);
		ts.add(s6);
		ts.add(s7);
		ts.add(s8);

		for (Student s : ts) {
			System.out.println(s.getName() + "---" + s.getAge());
		}
	}
}
//输出结果

总结

  1. Set和Collection实际上没有区别,只不过Set集合不记住元素的添加的顺序
  2. Set有三个子类:HashSet,LinkedSet和TreeSet
  3. HashSet的核心就是利用hash算法来存储元素的内容以及在判断相同的时候回引用指定类的equals和hashCode方法来判断是否为同一元素,因此要记住在实现自定义类的时候记得要重写equals和hashCode方法
  4. LinkedSet类唯一区别就是它会记录元素的添加顺序
  5. TreeSet利用红黑树作为存储结构,进行有序的存储,并且能进行自然排序和定制(比较器)排序