Set接口
Set接口也是继承自Collection接口,它的方法与Collection接口的方法基本一致,并没有对Collection接口进行功能上的扩充。它与List接口的不同就是:Set接口中的元素是无序的,并且都会以某种规则保证存入的元素不会重复。
再Set接口下常见的三个实现类是HashSet、LinkedHashSet和TreeSet。HashSet是根据对象的哈希值确定元素在集合中的储存位置,具有良好的存取和查找功能;LinkedHashSet是链表和哈希表组合的一个数据存储结构;TreeSet则以二叉树的方式存储元素,它可以对集合中的元素进行排序。
HashSet
Set接口的实现类都是无序不重复的,向HashSet存储元素的时候是需要先用hashCode()方法确定元素的储存位置,然后再调用equals()方法确保该位置没有重复元素。Set接口和List接口存取元素的方式是一样的,其中的方法基本也和List接口没有太大差别。
它的无序性:
package ti;
import java.util.HashSet;
public class Ti13 {
public static void main(String[] args) {
HashSet<String> hs=new HashSet<String>();
hs.add("张三");
hs.add("李四");
hs.add("王五");
hs.add("王五");
for(String s:hs){
System.out.println(s);
}
}
}
这其中输出的顺序和输入的顺序是不一样的,这就是我们所说的无序性和不重复性。在向集合中存入元素的时候,为了保证结合正常工作,在存入元素的时候是需要重写Object类中的hashCode()和equals()方法,在我们用泛型指定HashSet集合中的元素类型时,这个指定的类型(包装类)就已经重写了hashCode()和equals()方法,所以我们可以不用重写hashCode()和equals()方法,但是当我们使用自定义的类对象时,我们就需要自己重写hashCode()和equals()方法,如果不重写这两种方法就可能会导致集合中出现重复的元素,如:
package ti;
import java.util.HashSet;
class Student13{
private int num;
private String name;
public Student13(int num, String name) {
this.num = num;
this.name = name;
}
public String toString(){
return num+":"+name;
}
}
public class Ti13 {
public static void main(String[] args) {
HashSet<Student13> hs=new HashSet<Student13>();
Student13 s1=new Student13(1,"张三");
Student13 s2=new Student13(2,"李四");
Student13 s3=new Student13(2,"李四");
hs.add(s1);
hs.add(s2);
hs.add(s3);
for(Student13 s:hs){
System.out.println(s.toString());
}
}
}
这是一个没有重写hashCode()和equals()方法的代码,但是它最终存入了两个相同的元素。
所以说我们在使用自己定义的类的时候需要重写hashCode()和equals()方法的,不然的话程序不知道如何根据我们想要的规则去判断是否有相同的元素。(这个重写是在我们自己定义的类中去重写的)
重写了hashCode()和equals()方法的:
package ti;
import java.util.HashSet;
class Student13{
private String num;
private String name;
public Student13(String num, String name) {
this.num = num;
this.name = name;
}
public String toString(){
return num+":"+name;
}
public int hashCode(){
return num.hashCode();
}
public boolean equals(Object obj){
//判断是否为同一个对象
if(this==obj){
return true;
}
//判断对象是否为Student3类型
if(!(obj instanceof Student13)){
return false;
}
//将Object类型的元素强转为Student13类型
Student13 stu=(Student13)obj;
//这个是只判断num是否一样,并不判断名字是否一样
//可以将这个num理解为我们日常生活中的身份证号,每个人都有自己的身份证号
//所以名字可以相同,但身份证号不能相同
boolean b=this.num.equals(stu.num);
return b;
}
}
public class Ti13 {
public static void main(String[] args) {
HashSet<Student13> hs=new HashSet<Student13>();
Student13 s1=new Student13("1","张三");
Student13 s2=new Student13("2","李四");
Student13 s3=new Student13("2","王二");
hs.add(s1);
hs.add(s2);
hs.add(s3);
System.out.println(hs);
}
}
这个是正确的结果:
LinkedHashSet集合
HashSet集合中存储的元素无序的,所以说我们如果想要元素的存取顺序一致的话,我们可以使用HashSet的子类LinkedHashSet类,LinkedHashSet类和LinkedList类都是使用了双向链表来维护内部元素的关系。
例:
package ti;
import java.util.HashSet;
import java.util.LinkedHashSet;
class Student13{
private String num;
private String name;
public Student13(String num, String name) {
this.num = num;
this.name = name;
}
public String toString(){
return num+":"+name;
}
public int hashCode(){
return num.hashCode();
}
public boolean equals(Object obj){
//判断是否为同一个对象
if(this==obj){
return true;
}
//判断对象是否为Student3类型
if(!(obj instanceof Student13)){
return false;
}
//将Object类型的元素强转为Student13类型
Student13 stu=(Student13)obj;
//这个是只判断num是否一样,并不判断名字是否一样
//可以将这个num理解为我们日常生活中的身份证号,每个人都有自己的身份证号
//所以名字可以相同,但身份证号不能相同
boolean b=this.num.equals(stu.num);
return b;
}
}
public class Ti13 {
public static void main(String[] args) {
LinkedHashSet<Student13> hs=new LinkedHashSet<Student13>();
Student13 s1=new Student13("1","张三");
Student13 s2=new Student13("2","李四");
Student13 s3=new Student13("3","王二");
hs.add(s1);
hs.add(s2);
hs.add(s3);
System.out.println(hs);
}
}
TreeSet集合
TreeSet集合是Set接口的另一个实现类,内部采用二叉树存储元素,这样的结构一是可以保证集合中没有重复元素,二是可以对元素进行排序。
所谓二又树就是每个节点最多有两个子节点的有序树。一个节点及其子节点组成的树称为子树,通常左侧的子树称为左子树,右侧的子树称为右子树,其中左子树上的元素都小于它的根节点,而右子树上的元素都大于它的根节点。在二叉树中,对于同-层的元素,左边的元素总是小于右边的元素。
由于TreeSet的特殊性,TreeSet有一些在继承Set接口的基础上实现了一些特有的方法:
Object first(); //返回集合的第一个元素
Object last(); //返回集合的最后一个元素
Object lower(Object o); //返回集合中小于给定元素的最大元素,如果没有返回null
Object floor(Object o); //返回集合中小于或等于给定元素的最大元素,如果没有返回null
Object higher(Object o); //返回集合中大于给定元素的最小元素,如果没有返回null
Object ceiling(Object o); //返回集合中大于或等于给定元素的最小元素,如果没有返回null
Object pollFirst(); //移除并返回集合的第一个元素
Object pollLast(); //移除并返回集合的最后一个元素
例:
package ti;
import java.util.TreeSet;
public class Ti14 {
public static void main(String[] args){
//创建TreeSet集合对象
TreeSet<Integer> t=new TreeSet<Integer>();
//向TreeSet集合中添加元素
t.add(13);
t.add(14);
t.add(34);
t.add(2);
t.add(24);
//
System.out.println("全部元素为:"+t);
//获取集合的第一和最后个元素
System.out.println("第一个元素:"+t.first());
System.out.println("最后一个元素:"+t.last());
//比较并且获取小于15的最大元素
System.out.println("比较并且获取小于15的最大元素:"+t.lower(15));
//比较并且获取大于15的最小元素
System.out.println("比较并且获取大于15的最小元素:"+t.higher(15));
//删除元素
System.out.println("删除第一个元素为:"+t.pollFirst());
System.out.println("删除最后一个元素为:"+t.pollLast());
}
}
结果如下:
自然排序
自然排序要求的是TreeSet集合存储的元素所在类必须实现Comparable接口,并且重写compareTo()方法,然后TreeSet集合就会对该类型的元素使用compareTo()方法进行比较。
compareTo()方法将当前对象与指定的对象按照顺序进行比较,返回值为一个整数,其中,返回值为负整数表示当前对象小于指定对象,等于零表示当前对象等于指定对象,正整数表示当前对象大于指定对象,默认根据比较结果进行排序。
下面是一个TreeSet集合使用自定义的Student对象实现Comparable接口并重写compareTo的例子:
package ti;
import java.util.TreeSet;
public class Ti15 {
public static class Student implements Comparable{
private int age;
public Student(){}
public Student(String name,int age) {
this.age = age;
this.name = name;
}
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//重写compareTo()方法
public int compareTo(Object obj){
Student stu=(Student)obj;
//先比较age,在比较name
if(this.age-stu.age>0)
return 1;
if(this.age-stu.age==0) {
return this.name.compareTo(stu.name);
}
return -1;
}
}
public static void main(String[] args){
//创建TreeSet集合
TreeSet<Student> ts=new TreeSet<Student>();
//创建Student对象
Student s1=new Student("abaaba",12);
Student s2=new Student("asdcas",13);
Student s3=new Student("jfadad",12);
Student s4=new Student("habhd",14);
Student s5=new Student("habhd",14);
Student s6=new Student("nbfjar",13);
//向TreeSet集合中添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6)
//输出TreeSet集合
// System.out.println(ts);
for(Student s:ts){
System.out.println(s.getName()+":"+s.getAge());
}
}
}
运行会产生下列结果:
排序是按照年龄从小到大排列的,相同元素也不会重复记录。
自定义排序
如果不想实现Comparable接口或者不想按照实现了Comparable接口的类中的compareTo方法的规则进行排序,就可以自己定义比较器的方式对TreeSet集合中的元素进行自定义规则的排序。实现了Comparator接口的类都是一个自定义比较器,可以在自定义比较器的compare()方法中自定义排序规则。
下面举一个例子,是先根据Studnet类的年龄去计较,从小到大,年龄相同再根据名字排序:
package ti;
import com.sun.source.tree.Tree;
import java.util.Comparator;
import java.util.TreeSet;
public class Ti16 {
public static class Student{
private String name;
private String age;
public Student(String name, String age) {
this.name = name;
this.age = age;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString(){
return name+":"+age;
}
}
public static void main(String[] args){
TreeSet ts=new TreeSet(new Comparator(){
//重写方法
@Override
public int compare(Object o1,Object o2){
Student s1=(Student)o1;
Student s2=(Student)o2;
if(!s1.getAge().equals(s2.getAge())){
return s1.getAge().compareTo(s2.getAge());
}else{
return s1.getName().compareTo(s2.getName());
}
}
});
ts.add(new Student("ahdahd","12"));
ts.add(new Student("abhd","13"));
ts.add(new Student("bfasd","14"));
ts.add(new Student("ahbdww","12"));
System.out.println(ts);
}
}
产生的结果如下: