Set集合
Set集合中的对象不按照特定的方式排列,只是简单地把对象加入到集合中,但是Set集合中不能包含重复对象。Set集合由Set接口和Set接口的实现类组成。Set接口继承了Collection接口,因此包含Collection接口的所有方法。Set接口中的方法和Collection接口一致。
Set接口的常见实现类:
HashSet:内部数据结构是哈希表,是不同步的。
HashSet如何保证该集合中元素的唯一性:
是通过对象的hashCode和equals方法啊来完成对象的唯一性的。如果对象的hashCode值不同,那么不用判断equals方法,就直接存储到哈希表中。如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true。如果为true,视为相同元素,不存。如果为false。那么视为不同元素,就进行存储。
如果元素存储到HashSet集合中,必须覆盖hashCode方法和equals方法。一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals方法和hashCode方法,建立对象判断是否相同的依据。
HashSet是无序的,但是下面的子接口LinkedHashSet是具有可预知迭代顺序的set接口的哈希表和链表实现的,有序。
TreeSet:可以对Set集合中的元素进行指定顺序的排序。是不同步的。
TreeSet如何保证该集合中元素的唯一性:
就是根据比较方法compareTo的返回结果是否是0,是0,就判定为相同元素,不存。
TreeSet对元素进行排序的方式一:
让元素自身具备比较功能,就需要实现Comparable接口。覆盖CompareTo方法。
如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。这个时候可以使用TreeSet集合的第二种排序方式。
TreeSet集合的第二种排序方式二:
让集合自身具备比较功能,定义一个类实现Comparator接口。覆盖compare方法。将该类对象作为参数传递给TreeSet集合的构造函数。相对来说,这个方法比较常见。
HashSet例一:往hashSet集合中存储Person对象,如果姓名和年龄相同,视为同一个人(相同元素)。
先新建一类Person:
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
/**
* 覆盖Object类中的hashCode方法。建立Person对象自己特点的哈希值算法。
*/
public int hashCode(){
//System.out.println(this+"...hascode...");
return name.hashCode() + age*27;//乘以27是为了尽量保证哈希值唯一。
}
/**
* 覆盖Object类中的equals方法,建立Person对象判断是否相同的依据。 根据Person自身的特点来判断。
*/
public boolean equals(Object obj){
//System.out.println(this+"...equals..."+obj);
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.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 String toString() {
return name+":"+age;
}
再新建HashSetTest类,进行存储与取出Person对象的操作:
/*
往hashSet集合中存储Person对象,如果姓名和年龄相同,视为同一个人(相同元素)。
* */
public class HashSetTest {
public static void main(String[] args) {
HashSet hs=new HashSet();
/*HashSet集合数据结构是哈希表,所以存储元素的时候,使用的元素的hashCode方法来确定位置,如果位置相同,再通过元素的equals来确定是否相同。*/
hs.add(new Person("abc1",21));
hs.add(new Person("abc2",22));
hs.add(new Person("abc3",23));
hs.add(new Person("abc4",24));
hs.add(new Person("abc2",22));
Iterator it=hs.iterator();
while(it.hasNext()){
Person p=(Person)it.next();//因为存入集合容器中的元素都会向上转型为Object类,会丢失掉Person类的特有属性,所以必须强制向下转型为Person类,才可以拿到它的name和age属性;另外,由于同时要拿两个属性,所以等于说it.next()取到的对象不止使用一次,必须用一个变量来接收。
System.out.println(p.getName()+":"+p.getAge());
}
}
}
结果输出为:
abc3:23
abc2:22
abc4:24
abc1:21
现在回过头看另外一个问题:由于List集合中时允许重复的元素存在的。如何可以定义一个功能去除ArrayList集合中的重复元素呢?
从原理上讲,ArrayList判断相同的依据是equals方法 ,而HashSet判断相同的方法是equals方法和hashCode方法同时判断。因此,对于相同内容不同地址的元素,ArrayList都会判定为不同元素,而HashSet则会判定诶相同元素,因此List内允许元素重复而Set里面不允许。
这个问题的解决思路就是:
1. 定义一个临时容器,用于存储唯一性的元素;
2. 迭代已有的集合,将每一个迭代到的元素都与新集合中判断是否包含。 如果包含就不存储,如果不包含就存储到新集合中。
3. 迭代结束,新集合中存储的都是不重复的元素,返回新集合。
代码如下:
public class ArrayListTest {
public static void main(String[] args) {
// singleDemo();
ArrayList al=new ArrayList();
al.add(new Person("lisa1",21));
al.add(new Person("lisa2",22));
al.add(new Person("lisa3",23));
al.add(new Person("lisa4",24));
al.add(new Person("lisa3",23));
al.add(new Person("lisa4",24));
System.out.println(al);
al=getSingleElement(al);
System.out.println(al);
System.out.println(al.remove(new Person("lisa2",22)));//remove底层依据用的也是equals
System.out.println(al);
}
public static void singleDemo() {//元素不是自定义的对象
ArrayList al=new ArrayList();
al.add("abc1");
al.add("abc2");
al.add("abc1");
al.add("abc2");
al.add("abc1");
al.add("abc");
System.out.println(al);
al=getSingleElement(al);
System.out.println(al);
}
public static ArrayList getSingleElement(ArrayList al) {
//定义一个临时容器
ArrayList temp=new ArrayList();
//迭代al集合
Iterator it=al.iterator();
while(it.hasNext()){
Object obj=it.next();
//判断被迭代到的元素是否在临时容器中存在
if(!temp.contains(obj))//contains底层依据也是equals方法
temp.add(obj);
return temp;
}
}
代码结果输出如下:
[lisa1:21, lisa2:22, lisa3:23, lisa4:24, lisa3:23, lisa4:24]
[lisa1:21, lisa2:22, lisa3:23, lisa4:24]
true
[lisa1:21, lisa3:23, lisa4:24]
可见,已经解决问题。
此外,要注意list和set的本质区别是元素是否重复,而不是是否有序。HashSet下有个子类LinkedHashSet,它呃实现就是有序的:
package hashset.demo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
//有序的HashSet,所以list和set的本质区别是元素是否重复,而不是是否有序
public static void main(String[] args) {
HashSet hs=new LinkedHashSet();
hs.add("abc1");
hs.add("abc2");
hs.add("abc3");
hs.add("acb3");
hs.add("abc4");
hs.add("abc4");
Iterator it =hs.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
TreeSet
排序的方式一:自然排序
通过元素自身具备的比较性实现了Comparable接口的compareTo方法。
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add("anny");
ts.add("cindy");
ts.add("wendy");
ts.add("sam");
ts.add("cherrytree");
ts.add("alice");
for(Iterator it =ts.iterator(); it.hasNext();){
System.out.println(it.next());
}
}
}
输出结果:
alice
anny
cherrytree
cindy
sam
wendy
再通过存储Person类打印输出为例:自定义类有时候也有自然顺序的比较性,但如果不想要这种自然顺序的比较性或者自定义类不具备自然顺序的比较性,而需要自己指定自定义类的比较性,有两个方法,第一,实现Comparable接口,让Person具备自然顺序,再覆盖compareTo方法;第二,实现Comparator接口,覆盖compare方法,这种方式称为比较器排序。
代码如下:
第一,实现Comparable接口
public class Person implements Comparable {
private String name;
private int age;
public Person() {
super();
}
/**
* @param name
* @param age
*/
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
//System.out.println(this+"...hascode...");
return name.hashCode()+age*27;
}
@Override
public boolean equals(Object obj) {
if(this==obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
//System.out.println(this+"...equals..."+obj);
Person p=(Person)obj;
return this.name.equals(p.name)&&this.age==p.age;
}
@Override
public String toString() {
return this.name+":"+this.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 compareTo(Object o) {
/*
* 按照Person对象的年龄进行排序。从小到大。
* 如果想从大到小,只需要将this.age>p.age改为this.age<p.age即可,或者将返回值1和-1对调。
*/
// Person p = (Person)o;
// if(this.age>p.age)
// return 1;
// if(this.age==p.age)
// return this.name.compareTo(p.name);
// return -1;
Person p = (Person)o;
int temp = this.age - p.age;
return temp==0?this.name.compareTo(p.name):temp;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
agedemo();
}
public static void agedemo() {
TreeSet ts=new TreeSet();
/*
* 以Person对象的年龄进行从小到大的排序
* */
ts.add(new Person("alice",22));
ts.add(new Person("bob",23));
ts.add(new Person("cindy",24));
ts.add(new Person("ann",28));
ts.add(new Person("sally",22));
Iterator it=ts.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
System.out.println(p.getName()+":"+p.getAge());
}
}
结果输出:
alice:22
sally:22
bob:23
cindy:24
ann:28
第二,实现Comparator接口
public class ComparatorByName implements Comparator {
@Override
/*
* 创建了一个根据Person类的name进行排序的比较器。
* */
public int compare(Object o1, Object o2) {
Person p1=(Person)o1;
Person p2=(Person)o2;
int temp=p1.getName().compareTo(p2.getName());
return temp==0?p1.getAge()-p2.getAge():temp;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
namedemo();
}
public static void demo2() {
TreeSet ts=new TreeSet(new ComparatorByName());
/*
* 以Person对象的姓名排序
* */
ts.add(new Person("alice",22));
ts.add(new Person("bob",23));
ts.add(new Person("cindy",24));
ts.add(new Person("ann",28));
ts.add(new Person("sally",22));
Iterator it=ts.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
System.out.println(p.getName()+":"+p.getAge());
}
}
输出:
alice:22
ann:28
bob:23
cindy:24
sally:22
也可以定义长度为顺序的比较器:
public class ComparatorByLength implements Comparator{
@Override
public int compare(Object o1, Object o2) {
String s1=(String)o1;
String s2=(String)o2;
int temp=s1.length()-s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
demo1();
}
public static void demo1() {
TreeSet ts=new TreeSet(new ComparatorByLength());
ts.add("abc");
ts.add("zaaas");
ts.add("aa");
ts.add("nbas");
ts.add("cba");
Iterator it=ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}