这一篇我们来介绍Collection接口的另一个子接口,Set接口。Set是个接口,元素不可以重复,是无序的。Set接口中的方法和Collection的一致。
A、Set的子类:
1、HashSet:此类实现的Set接口,由哈希表(实际上是一个HashMap)实例支持,它不保证Set的迭代顺序,但是允许使用null元素。内部结构是哈希表,是不同步的。
哈希表确定元素是否相同的步骤如下:
一、 判断的是两个元素的哈希值(hashcode)是否相同。如果相同,再判断两个对象的内容是否相同,如果内容相同就表示两个元素是相同的。
二、 判断哈希值是否相同,其实判断的是对象的hashCode()的方法。判断内容是否相同,用的是equals()方法。
三、 如果哈希值不同,那就不需要进行equals判断。
HashSet集合数据结构是哈希表,所以存储元素的时候,使用元素的hashCode()方法来确定位置,如果位置相同,再通过元素的equals方法来确定是否相同。
练习题:定义功能去除ArrayList的重复元素?
注意:像ArrayList集合,在判断元素是否相同的时候,仅仅需要判断equals()方法。因为数据结构不同,对元素的判断依据也就一样。
代码如下:
package com.wq.person; public class Person extends Object { private String name; private int age; public Person() { super(); // TODO Auto-generated constructor stub } public Person(String name, int age) { super(); this.name = name; this.age = 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; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Person)) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { // TODO Auto-generated method stub return name+"...."+age; } } //hashset测试程序 package com.wq.hashset; import java.util.HashSet; import java.util.Iterator; import com.wq.person.Person; public class testHashSet { public static void main(String[] args) { // TODO Auto-generated method stub HashSet hs=new HashSet();//实例化一个hashset对象 hs.add(new Person("lisi1",21)); hs.add(new Person("lisi2",22)); hs.add(new Person("lisi3",23)); hs.add(new Person("lisi4",24)); hs.add(new Person("lisi4",24));//如果想在迭代中不出现相同的元素,需要通过覆写Person类的hashcode和equals方法 //使用迭代器进行迭代 for(Iterator it=hs.iterator();it.hasNext();){ Person pe=(Person)it.next(); System.out.println(pe.getName()+"---"+pe.getAge()); } } }
特别注意:如果希望元素不重复,但是要有序,这时可以考虑使用LinkedHashSet,它是HashSet的子接口。
2、TreeSet接口,可以对Set集合的元素进行排序,不是同步的。它底层的数据结构是红黑树。
判断元素唯一性的方式就是根据比较方法compareTo()的返回结果,判断返回的结果是否为0,是0就表示相同的元素且不会存储在Set中的(Set是不允许存储重复的元素的)。如果返回的结果不是0,则表示的是不相同。
TreeSet对元素进行排序的方式一:让元素具备比较功能,元素需要实现comparable接口,覆盖CompareTo()方法。
如果不要按照对象具备的自然顺序进行排序,如果对象不具备自然顺序。(即对象没有比较性,或者具备的比较性不是我们所需要的)这个时候怎么办???
TreeSet对元素进行排序的方式二:(生成一个比较器)让集合自身具备比较的功能,定义一个类,实现Comparator接口,覆盖compare方法,将该类的对象作为参数传递给TreeSet集合的构造函数。
代码演示如下:
//Person类 package com.wq.person; //需要实现Comparable接口 public class Person extends Object implements Comparable{ private String name; private int age; public Person() { super(); // TODO Auto-generated constructor stub } public Person(String name, int age) { super(); this.name = name; this.age = 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; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Person)) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { // TODO Auto-generated method stub return name+"...."+age; } //需要覆写的compareTo方法 //这里我的思路是先比较age的大小,如果age相等,则 //接着比较name的字典顺序大小 @Override public int compareTo(Object o) { // TODO Auto-generated method stub Person per=(Person) o;//实现强制类型转换 if(this.age>per.age){ return 1; }else if(this.age<per.age){ return -1; }else{//如果年纪相等就比较名字的字典顺序大小 int temp=this.name.compareTo(per.name); return temp; } } } //自己写的Comparator接口 package com.wq.comparator; import java.util.Comparator; import com.wq.person.Person; public class ComparatorByName implements Comparator { /** * 覆写compare方法,生成一个比较器。这里我们用name来作为比较 */ @Override public int compare(Object o1, Object o2) { // TODO Auto-generated method stub Person p1=(Person)o1; Person p2=(Person)o2; int temp=p1.getName().compareTo(p2.getName());//获取两个对象的name比较的结果 return temp==0?p1.getAge()-p2.getAge():temp; } } //测试类。用于测试CompareTo()方法和Comparable接口 package com.wq.TreeSet; import java.util.Iterator; import java.util.TreeSet; import com.wq.comparator.ComparatorByName; import com.wq.person.Person; public class testTreeSet { public static void main(String[] args) { // TODO Auto-generated method stub //测试实现Comparable接口的比较功能 System.out.println("测试实现Comparable接口的比较功能"); testComparableDemo(); System.out.println("测试实现Comparator接口,作为一个比较器来进行比较的功能"); //测试实现Comparator接口,作为一个比较器来进行比较的功能 testComparatorDemo(); } public static void testComparatorDemo() { TreeSet ts=new TreeSet(new ComparatorByName());//实例化对象,并且确定比较器 //添加自定义对象 //在添加元素时,自定义的对象必须实现Comparator接口,同时要覆盖compare方法 //否则就会报错 ts.add(new Person("zhangsan",22)); ts.add(new Person("zhaoliu",24)); ts.add(new Person("zhouqi",25)); ts.add(new Person("wuba",28)); ts.add(new Person("lisi",26)); ts.add(new Person("wangwu",25)); for(Iterator it=ts.iterator();it.hasNext();){ Person per=(Person)it.next(); System.out.println(per); } } public static void testComparableDemo() { TreeSet ts=new TreeSet();//实例化对象, //添加自定义对象 //在添加元素时,自定义的对象必须实现Comparable接口,同时要覆盖compareTo方法 //否则就会报错 ts.add(new Person("zhangsan",22)); ts.add(new Person("zhaoliu",24)); ts.add(new Person("zhouqi",25)); ts.add(new Person("wuba",28)); ts.add(new Person("lisi",26)); ts.add(new Person("wangwu",25)); for(Iterator it=ts.iterator();it.hasNext();){ Person per=(Person)it.next(); System.out.println(per); } } }
运行结果如下:
测试实现Comparable接口的比较功能(以年龄大小)
zhangsan....22
zhaoliu....24
wangwu....25
zhouqi....25
lisi....26
wuba....28
测试实现Comparator接口,作为一个比较器来进行比较的功能(以名字的字典顺序)
lisi....26
wangwu....25
wuba....28
zhangsan....22
zhaoliu....24
zhouqi....25
练习:TreeSet集合练习,字符串长度的排序
分析:Java代码中。TreeSet默认的会为字符串进行字典顺序大小的排序,但这不符合我们的要求。这个时候怎么办,我们必须要用到比较器。即comparator接口来实现。
具体代码实现如下:
//自定义的比较器 package com.wq.comparator; import java.util.Comparator; import com.wq.person.Person; /* * 生成一个比较器,用来比较字符串的长度大小,如果长度相等则比较字符的字典顺序大小 */ public class ComparatorByLength implements Comparator { @Override public int compare(Object o1, Object o2) { // TODO Auto-generated method stub String str1=(String)o1; String str2=(String)o2; int temp=str1.length()-str2.length(); return temp==0?str1.compareTo(str2):temp; } } //测试类 package com.wq.TreeSet; import java.util.Iterator; import java.util.TreeSet; import com.wq.comparator.ComparatorByLength; import com.wq.person.Person; /** * 这个练习是对TreeSet集合的练习 * 主要是进行字符串的长度的排序 * @author LULEI * */ public class testTreeSetDemo { public static void main(String[] args) { // TODO Auto-generated method stub TreeSet ts=new TreeSet(new ComparatorByLength());//在构造函数中添加相应的构造器 ts.add("asdfdf"); ts.add("dds"); ts.add("fghf"); ts.add("as"); ts.add("t"); ts.add("fdsfsg"); for(Iterator it=ts.iterator();it.hasNext();){ Object o=(Object)it.next(); System.out.println(o); } } }
运行结果如下:
t
as
dds
fghf
asdfdf
fdsfsg
--------------------------------------------------------------------*--------------------------------------------------------------------------------------------------
我们已经介绍了List和Set接口,接下来总结一下。
总结:List: 1---ArrayList 2---LinkedList
Set 1---HashSet --LinkedHashSet
2---TreeSet
注意,后缀名就是该集合所属的体系,前缀名就是该集合的数据结构。
1、 看到array:就要想到数组,就要想到查询快,有角标。如果不希望出现重复的元素则需要覆盖equals()方法。
2、看到link就要想到链表,就要想到增删快,就要想到add/get/remove+first(last)的方法。
3、看到hash就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashCode()和equals()方法。
4、看到tree就要想到二叉树,就要想到排序,就要想到两个接口:comparator和comparable。
并且通常这些常用的容器都是不同步的。