Set集合接口

Set接口与List接口最大的不同在于Set接口中的内容是不允许重复的。Set接口并没有对Collection接口进行扩充,而List对Collection进行了扩充。因此,在Set接口中没有get()方法。
在Set子接口中有两个常用子类:HashSet(无序存储)和TreeSet(有序存储)。
1.HashSet集合
HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。当向HashSet集合中添加一个元素时,首先会调用该元素的hashCode()方法来确定元素的存储位置,然后再调用该元素对象的equals()方法来确保该位置没有重复元素。
示例1:

import java.util.HashSet;

public class TestDemo {
	public static void main(String[] args) {
		HashSet set = new HashSet();
		set.add("001");
		set.add("002");
		set.add("003");
		set.add("003");
		System.out.println(set);
	}
}

运行结果:
在这里插入图片描述
解释:
HashSet集合之所以能确保不出现重复的元素,是因为它在存入元素是做了很多的工作。当调用HashSet集合的add()方法存入元素是时,首先调用当前存入元素的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置;如果该位置上没有元素,则直接将元素存入;如果该位置上有元素存在,则会调用equals()方法让当前存入的元素以此与该位置上的元素进行比较。如果返回的结果为false就将该元素存入集合;返回为true则说明有重复元素,就将该元素舍弃。

示例2:
将开发者自定义的类型对象存入HashSet,结果如何呢?

import java.util.HashSet;

class Student {
	String id;
	String name;

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

	public String toString() {
		return id + ":" + name;
	}
}

public class TestDemo {
	public static void main(String[] args) {
		HashSet hs = new HashSet();
		Student stu1 = new Student("1", "Bob");
		Student stu2 = new Student("2", "Lily");
		Student stu3 = new Student("2", "Lily");
		hs.add(stu1);
		hs.add(stu2);
		hs.add(stu3);
		System.out.println(hs);
	}
}

运行结果:
在这里插入图片描述
提问:运行结果出现了两个相同的学生信息“1:Lily”,这样的学生信息应该被视为重复元素,不允许同时出现在HashSet集合中?
之所以没有去掉重复元素是因为在定义Student类时没有重写hashCode()和equals()方法,因此创建的这两个学生对象stu2和stu3所引用的对象地址不同,所以HashSet集合会认为这是两个不同的对象。

示例3:
接下来对Studeng类进行改写,增加重写的hashCode()方法和equals()方法,假设id相同的学生就是同一个学生

import java.util.HashSet;

class Student {
	String id;
	String name;

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

	public String toString() {
		return id + ":" + name;
	}

	// 重写hashCode方法
	public int hashCode() {
		return id.hashCode();// 返回id属性的哈希值
	}

	// 重写equals方法
	public boolean equals(Object obj) {
		if (this == obj) { // 判断是否是同一个对象
			return true; // 如果是,直接返回true
		}
		if (!(obj instanceof Student)) { // 判断对象是否是Studeng类型
			return false; // 如果对象不是Student类型,返回false
		}
		Student stu = (Student) obj; // 将对象强转为Student类型
		boolean b = this.id.contentEquals(stu.id);// 判断id值是否相同
		return b;// 返回判断结果
	}
}

public class TestDemo {
	public static void main(String[] args) {
		HashSet hs = new HashSet();
		Student stu1 = new Student("1", "Bob");
		Student stu2 = new Student("2", "Lily");
		Student stu3 = new Student("2", "Lily");
		hs.add(stu1);
		hs.add(stu2);
		hs.add(stu3);
		System.out.println(hs);
	}
}

运行结果:
在这里插入图片描述
解释:Student类重写了Object类的hashCode()和equals()方法。在hashCode()方法中返回id属性的哈希值,在equals()方法中比较对象的id值是否相等,并返回结果。当调用HashSet集合的add()方法添加stu3对象时,发现它的哈希值与stu2对象相同,而且stu2.equals(stu3)返回true,HashSet集合认为两个对象相同,因此重复的Student对象被舍弃。
2.TreeSet集合
在这里插入图片描述
小试牛刀:(方法应用)

import java.util.TreeSet;

public class TestDemo {
	public static void main(String[] args) {
		// 创建Set集合
		TreeSet ts = new TreeSet();
		// 1.向TreeSet集合中添加元素
		ts.add(3);
		ts.add(9);
		ts.add(2);
		ts.add(20);
		System.out.println("创建的TreeSet集合为:" + ts);

		// 2.获取首尾元素
		System.out.println("TreeSet集合首元素为:" + ts.first());
		System.out.println("TreeSet集合尾元素为:" + ts.last());

		// 3.比较并获取元素
		System.out.println("集合中小于或等于9的最大的一个元素为:" + ts.floor(9));
		System.out.println("集合中大于10的最小的一个元素为:" + ts.higher(10));
		System.out.println("集合中大于100的最小的一个元素为:" + ts.higher(100));

		// 删除元素
		Object first = ts.pollFirst();
		System.out.println("删除的第一个元素是:" + first);
		System.out.println("删除第一个元素后TreeSet集合变为" + ts);
	}
}

运行结果:
在这里插入图片描述
这些元素都能按照一定的顺序排列,原因是每次向TreeSet集合中存入一个元素时,就会将该元素与其它元素进行比较,最后将它插入到有序的对象序列中。
集合中的元素在进行比较时,都会调用CompareTo()方法,该方法是Compararble接口中定义的,因此要想对集合中的元素进行排序,就必须实现Comparable接口。Java中大部分的类都实现了Comparable接口,并默认实现了接口中的CompareTo()方法,如:Integer、Double、String。
在实际开发中,除了会向TreeSet集合中存储一些Java中默认的类型数据外,还会存储一些用户自定义的类型数据,如Student类型数据等。由于这些自定义类型的数据没有实现Comparable接口,因此也就无法直接在TreeSet集合中进行排序操作。为了解决这个问题,Java提供了两种TreeSet排序规则,分别为:自然排序和定制排序。
2.1自然排序
自然排序要求向TreeSet集合中存储的元素所在类必须实现Comparable接口,并重写compareTo()方法,然后TreeSet集合就会对该类型元素使用compareTo()方法进行比较,并默认进行升序排序。

import java.util.TreeSet;

class Teacher implements Comparable {
	String name;
	int age;

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

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

	// 重写CompareTo接口的compareTo()方法
	public int compareTo(Object obj) {
		Teacher s = (Teacher) obj;
		// 定义比较方式,先比较年龄age,再比较name
		if (this.age - s.age > 0) {
			return 1;
		}
		if (this.age - s.age == 0) {
			return this.name.compareTo(s.name);
		}
		return -1;
	}
}

public class TestDemo {
	public static void main(String[] args) {
		TreeSet ts = new TreeSet();
		ts.add(new Teacher("Bob", 19));
		ts.add(new Teacher("Lily", 18));
		ts.add(new Teacher("Tom", 19));
		ts.add(new Teacher("Lily", 18));
		System.out.println(ts);
	}
}

运行结果:
在这里插入图片描述
解释:Teacher类实现了Comparable接口,并重写了compareTo()方法。在coppareTo()方法中,首先针对age值进行比较,根据比较结果返回-1和1,当age相同时,再对name进行比较。TreeSet集合会将重复的元素去掉。
2.2定制排序
有时候,用户自定义的类型数据所在的类没有实现Comparable接口或者对于实现了Comparable接口的类不想按照定义的compareTo()方法进行排序。例如希望存储在TreeSet集合中的字符串可以按照长度而不是英文字母的顺序来进行排序,这时,可以通过在创建TreeSet集合时就自定义一个比较器来对元素进行定制排序。

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

class MyComparator implements Comparator {
	public int compare(Object obj1, Object obj2) {// 定制排序方式
		String s1 = (String) obj1;
		String s2 = (String) obj2;
		int temp = s1.length() - s2.length();
		return temp;
	}
}

public class TestDemo {
	public static void main(String[] args) {
		// 1.创建集合时,传入Comparator接口实现定制排序规则
		TreeSet ts = new TreeSet(new MyComparator());
		ts.add("Bob");
		ts.add("Lily");
		ts.add("Tomee");
		System.out.println(ts);
		// 2.创建集合时,使用Lambda表达式定制排序规则
		TreeSet ts2 = new TreeSet((obj1, obj2) -> {
			String s1 = (String) obj1;
			String s2 = (String) obj2;
			return s1.length() - s2.length();
		});
		ts2.add("Bob");
		ts2.add("Lily");
		ts2.add("Tomee");
		System.out.println(ts2);
	}
}

运行结果:
在这里插入图片描述
解释:
上例中,使用TreeSet集合的public TreeSet(Comparator<? super E>comparator)有参构造方法,分别传入Comparable接口实现MyComparator以及Lambda表达式两种参数方式创建了定制排序规则的TreeSet集合,当向集合中添加元素时,TreeSet集合就会按照定制的排序规则进行比较,从而使存入TreeSet集合中的字符串按照长度进行排序。
注意:在使用TreeSet集合存储数据时,TreeSet集合会对存入元素进行比较排序,所以为了保证程序的正常运行,一定要保证存入TreeSet集合中的元素是同一种数据类型。

3.重复元素判断(hashCode与equals方法
在使用TreeSet子类进行数据保存的时候,重复元素的判断依靠的Comparable接口完成的。但是这并不是全部Set接口判断重复元素的方式,因为如果使用的是HashSet子类,由于其跟Comparable没有任何关系,所以它判断重复元素的方式依靠的是Object类中的两个方法:
public native int hashCode();
public boolean equals(Object obj);
equals()的作用是用来判断两个对象是否相等,在Object里面的定义是:
public boolean equals(Object obj){
return (thisobj);
}
**这说明在我们实现自己的equals方法之前,equals等于
,而==运算符是判断两个对象是不是同一个对象,即它们的地址是否相等。而覆写equals更多的是追求两个对象在逻辑上的相等,咱可以说是值相等,也可以说是内容相等。**
hashCode用于返回对象的hash值,主要用于查找的快捷性,因为hashCode也是在Object对象中就有的,所以所有Java对象都有hashCode,在Hashtable和HashMap这一类的散列结构中,都是通过hashCode来查找在散列表中的位置的。
如果两个对象equals,那么他们的hashCode必然相等,但是hashCode相等,equals不一定相等。对象判断必须两个方法返回值都相等才判断为相同。
个人建议:
1.保存自定义对象的时候使用List接口;
2.保存系统类信息的时候使用Set接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值