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

-----------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
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值