您的位置:首页 > 职场人生

黑马程序员-集合类Collection之Set接口

2013-08-01 22:30 507 查看
-----------android培训java培训、java学习型技术博客、期待与您交流!
------------

一、定义:

Set具有与Collection接口完全一样的接口,因此没有任何额外的功能;其实Set就是Collection,只是行为不同。

 

二、特点:

无序的(存入和取出的顺序不一定一致),无索引,元素不可重复。

Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。

三、子类:

|--HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;

       |--LinkedHashSet:有序,是hashset的子类。

|--TreeSet:底层的数据结构是二叉树。对Set集合中的元素进行指定顺序的排序,默认是自然顺序。不同步。

四、HashSet

1、保证性元素唯一的原理:

是通过元素的两个方法,hashCode和equals来完成。

如果元素的HashCode值相同,才会继续调用equals方法来判断两元素是否相同:如果为true,那么视为相同元素,不存;如果为false,那么存储。

如果元素的HashCode值不同,不会调用equals,从而提高对象比较的速度。

注意:对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法。往HashSet里面存的自定义元素一定要复写hashCode和equals方法,以保证元素的唯一性!

2、哈希表的原理:

(1)对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值称为哈希值。

(2)哈希值就是这个元素的位置。要找元素时,先将该元素通过哈希算法算出哈希值,再通过哈希值到哈希表中去查找。

(3)如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。

(4)存储哈希值的结构,我们称为哈希表。

(5)既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。

3、对比:

对于ArrayList集合,判断元素是否存在,或者删元素,底层依据都是equals方法。

对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。

 

4、代码示例:

/*
往hashSet集合中存入自定义对象
姓名和年龄相同为同一个人,重复元素。
*/
import java.util.*;

/*
往哈希表中的存储的自定义对象,必须覆盖hashCode方法,和equals方法。如果不知道这个对象到底存储到哪个容器中去,
那就将hashCode,equals,toString全都覆盖,创建对象自身的判断相同的依据。
*/
class Person {

// 声明属性
private String name;
private int age;

// 构造方法
Person(String name, int age) {
this.name = name;
this.age = age;
}

// getter,setter方法
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;
}

// 覆盖Object类中的hashCode方法。建立Person对象自己特有的哈希值算法。
public int hashCode() {
System.out.println(this.name + "....hashCode");
final int NUMBER = 37;
return name.hashCode() + age * NUMBER;// *37是为了尽量保证哈希值唯一。
}

// 覆盖Object类中的equals方法,建立Person对象。判断是否相同的依据: 根据Person自身的特点来判断。
public boolean equals(Object obj) {

if (!(obj instanceof Person))
return false;

Person p = (Person) obj;
System.out.println(this.name + "...equals.." + p.name);

return this.name.equals(p.name) && this.age == p.age;
}

}

class HashSetTest {
public static void sop(Object obj) {
System.out.println(obj);
}

public static void main(String[] args) {
HashSet<Person> hs = new HashSet<Person>();

// 往哈希表中存储自定义对象。
hs.add(new Person("a1", 11));
hs.add(new Person("a2", 12));
hs.add(new Person("a3", 13));
hs.add(new Person("a2", 12));
hs.add(new Person("a4", 14));

Iterator<Person> it = hs.iterator();

while (it.hasNext()) {
Person p = (Person) it.next();
sop(p.getName() + "::" + p.getAge());
}
}
}

/*output:
a1....hashCode
a2....hashCode
a3....hashCode
a2....hashCode
a2...equals..a2
a4....hashCode
a1::11
a3::13
a2::12
a4::14
*/

 

五、TreeSet:

 

1、特点:

底层数据结构式二叉树。可以用于对Set集合进行元素的指定顺序排序。排序需要依据元素自身具备的比较性。线程不同步。

 

2、保证元素唯一性的方式:

参考Comparable接口的compareTo方法的返回值。也就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。

3、TreeSet集合排序有两种方式,Comparable和Comparator:

(1)自然排序(默认排序):

1)方法:

让元素自身具备比较性,需要添加的元素对象实现Comparable接口,覆盖compareTo方法。

如果元素不具备比较性,在运行时会发生ClassCastException异常。

所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。

依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。

2)注意:

在进行比较时,如果判断条件不唯一,比如,同姓名,同年龄,才视为同一个人。

在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。

 

3)代码示例:

/*
需求:
往TreeSet集合中存储自定义对象学生。
想按照学生的年龄进行排序。

记住:排序时,当主要条件相同时,一定判断一下次要条件。
*/

import java.util.*;

class Student implements Comparable<Object> {// 该接口强制让学生具备比较性。
private String name;
private int age;

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

public String getName() {
return name;

}

public int getAge() {
return age;
}

public int compareTo(Object obj) {
if (!(obj instanceof Student))
throw new RuntimeException("不是学生对象");

Student s = (Student) obj;

System.out.println(this.name + "....compareto....." + s.name);

if (this.age > s.age)
return 1;

if (this.age == s.age) {
return this.name.compareTo(s.name);
}
return -1;
}

}

class TreeSetTest1 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>();

ts.add(new Student("lisi02", 22));
ts.add(new Student("lisi007", 20));
ts.add(new Student("lisi09", 19));
ts.add(new Student("lisi08", 19));
ts.add(new Student("lisi007", 20));
ts.add(new Student("lisi01", 40));

Iterator<Student> it = ts.iterator();

while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println(stu.getName() + "..." + stu.getAge());
}
}

}

/*output:
lisi007....compareto.....lisi02
lisi09....compareto.....lisi02
lisi09....compareto.....lisi007
lisi08....compareto.....lisi007
lisi08....compareto.....lisi09
lisi007....compareto.....lisi007
lisi01....compareto.....lisi007
lisi01....compareto.....lisi02
lisi08...19
lisi09...19
lisi007...20
lisi02...22
lisi01...40
*/

 

(2)比较器:

1)使用条件及方法:

当元素自身不具备比较性(比如存储学生对象时)或者具备的比较性不是我们所需要的比较性时(比如想字符串的长度排序),此时就需要让集合自身具备自定义的比较性。那如何让集合自身具备比较性呢?可在集合初始化时,就让集合自身具备比较性,需要定义一个类(比较器),实现Comparator接口,并覆盖compare方法,将比较器对象作为实际参数传递给TreeSet集合的构造函数。

自定义比较器的方法:定义一个类,实现Comparator接口,覆盖compare方法。

注意:第二种方式较为灵活。当两种排序都存在时,以比较器为主。

2)优先考虑比较器的原因:

实现implements Comparable接口定义排序有局限性,实现此接口只能按comparaTo()定义的这一种方法排序,如果同一对象有多种排序方式,应该定义不同的比较器,比如说学生对象可以按学号,分数,年龄等进行多种排序。

3)自定义比较器实质:

让自己编写的类实现Comparator接口,重写Comparator中的比较方法compara(Object a,Objectb)

compara(Object a,Objectb)方法使用:

public  int compare(Object a,Object b){

  int i = b.对象属性- a.对象属性;

  return   i;   //返回0,表示this==obj  ;返回正数表示,this>obj; 返回负数,表示this<obj

}

 

4)代码示例:

//让学生信息按照学生成绩排序输出

import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
*
* 学术类
*
*/
class Student {

/* 声明属性和方法 */
private String name;
private Integer age;
private Integer score;

/* 构造方法 */
public Student(String name, Integer age, Integer score) {
super();
this.name = name;
this.age = age;
this.score = score;
}

/* getter,setter方法 */
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Integer getScore() {
return score;
}

public void setScore(Integer score) {
this.score = score;
}

@Override
// 重写String,toString方法
public String toString() {

return "姓名:" + name + "    年龄:" + age + "      成绩:" + score;
}

@Override
// 重写hasCode()方法
public int hashCode() {

return age * name.hashCode();
}

@Override
// 重写equals方法
public boolean equals(Object o) {

Student s = (Student) o;

return age == s.age && name.equals(s.name);

}

}

/**
* TreeSet测试类
*/

/* 创建学生成绩比较器 */
class StudentScoreComparator implements Comparator<Student> {

@Override
public int compare(Student o1, Student o2) {
int i = (int) (o2.getScore() - o1.getScore());
return i;
}
}

public class TreeSetTest2 {
public static void main(String[] args) {

Set<Student> ts = new TreeSet<Student>(new StudentScoreComparator()); // 创建TreeSet集合

Student stu1 = new Student("张三", 18, 85);
Student stu2 = new Student("李四", 19, 88);
Student stu3 = new Student("李红", 20, 60);
Student stu4 = new Student("张丽", 18, 90);

ts.add(stu1); // 将对象放进TreeSet集合中
ts.add(stu2);
ts.add(stu3);
ts.add(stu4);

Iterator<Student> it = ts.iterator(); // 声明迭代器

while (it.hasNext()) {
System.out.println(it.next());
}
}
}

/*output:
姓名:张丽    年龄:18      成绩:90
姓名:李四    年龄:19      成绩:88
姓名:张三    年龄:18      成绩:85
姓名:李红    年龄:20      成绩:60
*/

 

5)练习:

/*
练习:按照字符串长度排序。
字符串本身具备比较性。但是它的比较方式不是所需要的。
这时就只能使用比较器。
*/

import java.util.*;

class StrLenComparator implements Comparator<Object> {
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
if (num == 0)
return s1.compareTo(s2);
return num;
}
}

class TreeSetTest {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<String>(new StrLenComparator());

ts.add("abcd");
ts.add("cc");
ts.add("cba");
ts.add("aaa");
ts.add("z");
ts.add("hahaha");

Iterator<String> it = ts.iterator();

while (it.hasNext()) {
System.out.println(it.next());
}
}
}

/*output:
z
cc
aaa
cba
abcd
hahaha
*/

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java
相关文章推荐