java集合框架详解(二)、set接口

3.1.2 Set接口

首先我们应该知道Set是Collection接口的子接口

Set和Collection基本是一样的,但一点除外:Set无法记住添加的顺序,不允许包含重复的元素。一个不包含重复元素的 collection。更确切地讲,set 不包含满足e 1.equals(e 2)的元素对e 1和e 2,并且最多包含一个 null 元素。

有几点说明:

1、当试图添加两个相同元素进Set集合,添加操作会失败,add()方法返回false。

2、Set判断两个对象是否相等用equals,而不是使用==。

也就是说两个对象equals比较返回true,Set集合是不会接受这个两个对象的。

我们从JDK API中看到Set接口的所有已知实现类有:

AbstractSet, ConcurrentSkipListSet, CopyOnWriteArraySet, EnumSet, HashSet, JobStateReasons, LinkedHashSet, TreeSet

其中最常用的子类:a、HashSet:散列存放      b、TreeSet:有序存放

在详细介绍两个最长用子类之前,我们有必要明白HashSet与TreeSet是两种不同的数据存放格式,对应着不同的存入数据方式和取数据方式

a、HashSet:散列存放 

HashSet类是Set接口最常用的实现类,采用hash算法存储数据,具有良好的存储和查找功能。做如下几点说明:

1、它散列存储,不记录添加顺序。

2、排列顺序时,顺序有可能发生变化。同时线程不安全的,多个线程访问一个HashSet要使用同步代码。

3、HashSet集合元素值允许是null,但是最多只能有一个;

4、hash(翻译为哈希,或散列)算法的功能:

保证通过一个对象快速找到另一个对象,其算法价值体现在速度,可以保证查询快速执行。

当从HashSet中访问元素时,HashSet先计算该元素的hashCode(也就是该对象的hashCode方法返回值),然后直接到该HashCode对应的位置取出该元素。在这里对象的hashCode就好比是数组里的索引,但是不是索引。对于不同类型字段如何取得hashCode见下表(我们在程序实例中会说明如何在eclipse自动添加):

接着讲述HashSet元素添加。 当向HashSet集合中存入一个元素时,HashSet首先会调用该对象的hashCode()方法(是类Object的方法)来得到该对象的hashCode值,然后判断已经存储在集合中的对象的hashCode值是否与添加的对象的hashCode值一致:

若不一致:直接添加进去;

若一致:再进行equals方法(是父接口Collection中的方法)比较,equals方法如果返回true,表明对象已经添加进去了,就不会再添加新的对象了,否则添加进去。

如果我们重写了equals方法,也要重写hashCode方法,反之亦然。

注意:

1、HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode方法返回值也相等。

2、如果需要某个类的对象保存到HashSet集合中,覆写该类的equals()和hashCode()方法,应该尽量保证两个对象通过equals比较返回true时,他们的hashCode返回也相等。

下表是HashSet类得方法列表:由于方法比较简单,我们将不会在程序中详细演示(可对照上篇中的方法演示)


方法摘要


boolean


add(E e)

如果此 set 中尚未包含指定元素,则添加指定元素。


void


clear()

从此 set 中移除所有元素。


Object


clone()

返回此 HashSet 实例的浅表副本:并没有复制这些元素本身。


boolean


contains(Object o)

如果此 set 包含指定元素,则返回 true。


boolean


isEmpty()

如果此 set 不包含任何元素,则返回 true。


Iterator<E>


iterator()

返回对此 set 中元素进行迭代的迭代器。


boolean


remove(Object o)

如果指定元素存在于此 set 中,则将其移除。


int


size()

返回此 set 中的元素的数量(set 的容量)。

我们来看第一个程序实例:参照代码理解上述HashSet的知识重点。

import java.util.HashSet;
import java.util.Set;

/**
 * HashSet添加元素:
 *		Set set = new HashSet();
 *		set.add("tyy");
 *			先调用"tyy".hashCode(),判断set集合里是否已经有了"tyy".hashCode():
 *			两种可能:	1.元素tyy的hashCode不存在:	 就直接把tyy添加到set集合;
 *					    2.元素tyy的hashCode存在:      再比较元素"tyy"和与那个与元素"tyy"具有一样的Hashcode值的元素对象,
								 使用对象的equals比较。比较结果有两张,分别是false与true,
								 false: 就添加进set;
								 true: 表明是一个对象,add(Object e),就返回false,添加失败
 */

//下面的方法hashCode()与eaquals()方法是用来对HashSet集中的元素进行判断的
//AA类仅仅覆写hashCode()方法
class AA{
	public int hashCode() {
		return 0;
	}
}
//BB类仅仅覆写equals(Object obj)方法
class BB{
	public boolean equals(Object obj) {
		return true;
	}
}
//CC类即覆写hashCode()同时也覆写equals(Object obj)
/*接着向HashSet中添加元素。 当向HashSet集合中存入一个元素时,HashSet首先会调用该对象(此时要添加的元素)的hashCode()方法(是
 * 类Object的方法)来得到该对象的hashCode值,然后判断已经存储在集合中的对象的hashCode值是否与添加的对象的hashCode值一致:
 *					若不一致:直接添加进去;
 *					若一致:再进行equals方法(是父接口Collection中的方法)比较,equals方法如果返回true,表明对象已经添加进
 *去了,就不会再添加新的对象了,否则添加进去。如果我们重写了equals方法,也要重写hashCode方法,反之亦然。
*/

//如果需要某个类的对象保存到HashSet集合中,覆写该类的equals()和hashCode()方法
class CC{
	public int hashCode() {
		return 10;
	}
	public boolean equals(Object obj) {
		return true;
	}
}
public class HashSetDemo {
	public static void main(String[] args) {

		Set set = new HashSet();//创建一个HashSet对象set
		boolean b = set.add("tyy");//添加一个元素tyy
		System.out.println(b);//true
		b = set.add("tyy");//再次添加元素tyy
		System.out.println(b);//false,这里是false的原因是,当添加两个相同元素进Set集合,由于他们两的hashCode相等
		//所以添加操作会失败,add()方法返回false。

		set.add(null);
		set.add(null);
		System.out.println(set);//[null, tyy] 。已经添加过一个null了,set集最多存储一个null

		set.clear();
		System.out.println("===================");
		set.add(new AA());
		set.add(new AA());
		set.add(new BB());
		set.add(new BB());
		set.add(new CC());
		set.add(new CC());
		System.out.println(set);
	}
}

本段程序的运行结果如下

true

false

[null, tyy]

===================

[collection.[email protected],collection.[email protected],collection.[email protected],collection.[email protected], collection[email protected]]

我们分析一下结果,显然前四行的结果我们已在程序注释中给出解释了。5、6两行的结果原因是,在创建类元素AA与BB存储到hashSet集中时,要对元素进行hashCode与equals判断,而在类AA与BB中只是显示的覆写了两个判断方法中的一个,这也就是为什么AA、BB会类元素在结果中会出现两个,而CC只是一个。

接着我们看下一个程序示例:

import java.util.HashSet;
import java.util.Set;

class Stu{
	private long id;
	private String name;

	//对学生的ID号进行hashCode与equals判断,以区别学生。
	//点击鼠标右键,选择源码,选择生产hashCode与equals,选择生产的字段,OK
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (id ^ (id >>> 32));
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Stu other = (Stu) obj;
		if (id != other.id)
			return false;
		return true;
	}

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

		Set set = new HashSet();

		set.add(new Stu());

	}
}

b、TreeSet:有序存放

这里我们进入JDK API文档,在Treeset类中,我们可以看到它有下面所示的构造方法:


TreeSet()

构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。


TreeSet(Comparator<? super E> comparator)

构造一个新的空 TreeSet,它根据指定比较器进行排序

实际上它有四个构造方法,我们只看这两个。默认情况构造一个新的空set集时,该set集根据其元素的自然顺序进行排序(从小到大),就是构造方法一。而对于构造方法二,它是供我们自己设置排序方法的(比如我们可以设置成从大到小的顺序)

一而言之:TreeSet类使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序。

注意:参与排序的元素必须是同一类型的,不然会发生ClassCastException异常。

看示例:TreeSetDemo1

import java.util.Set;
import java.util.TreeSet;

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

		//构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
		Set set = new TreeSet();

		// 默认的比较就是 自然顺序,(升序)
        set.add("大哥");
		set.add("小姐");
		set.add("BB");
		set.add("AA");
		set.add("bb");
		set.add("aa");
		set.add("1");
		set.add("2");

		System.out.println(set);

	}
}

程序结果:[1, 2, AA, BB, aa, bb, 大哥, 小姐],可以看出它的输出顺序是什么样子的

TreeSet与HashSet相比额外的方法有:

Comparator comparator():返回当前Set使用的Comparator,若返回null,表示以自然顺序排序。

Object first() 返回此 set 中当前第一个(最低)元素。

Object last() 返回此 set 中当前最后一个(最高)元素。

SortedSet subSet(Object  fromElement, E toElement) 返回此 set 的部子集,其元素从 fromElement(包括)到 toElement(不包括)。

SortedSet headSet(Object  toElement)返回此 set 的部分子集,其元素严格小于 toElement。

SortedSet tailSet(Object  fromElement) 返回此 set 的部分子集,其元素大于等于 fromElement。

TreeSet的排序之自然排序

TreeSet会调用元素的compareTo(Object o)方法来比较元素之间的大小关系,然后将集合里的元素按升序排列。此时需要排序元素的类必须实现Compareble接口。并覆写其int compareTo(Object o)方法。

compareTo(Object o)方法简介:接口Compareble中方法,比较此对象“this”与指定对象“o”的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

对于TreeSet集合而言,判断两个对象相等的标准是: compareTo()方法比较返回 0

示例:

import java.util.Set;
import java.util.TreeSet;

/**
 * 根据人的年龄来排序,升序
 *
 */
class Person implements Comparable {
	private int age;
         // private Integer high;//high这里是引用数据类型
	public Person(int age) {
		this.age = age;
		//this.high= high;
	}

	public int compareTo(Object o) {
		if (o instanceof Person) {
			Person p = (Person) o;
			if (this.age > p.age) {
				return 1;
			} else if (this.age < p.age) {
				return -1;
			}
		}
	   return 0;
		//如果排序字段是引用数据类型的话
		//if (o instanceof Person) {//判断o是否是属于Person
		//	Person p = (Person) o;
		//	return this.high.compareTo(p.high);
		//}
		//return 0;
	}

	public String toString() {
		return this.age + "";
	}
}

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

		//构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
		Set set = new TreeSet();
		/*
		 * 默认的比较就是 自然顺序,(升序);要排序必须实现java.lang.Comparable接口 再覆写该接口里的
		 * compareTo(Objec o){
		 */

		set.add(new Person(17));
		set.add(new Person(28));
		set.add(new Person(56));
		set.add(new Person(31));
		System.out.println(set);		

	}
}

程序运行结果:[17, 28, 31, 56]

TreeSet的排序之定制排序

TreeSet的自然排序是根据元素的大小进行升序排序的,若想自己定制排序,比如降序排序,就可以使用Comparator接口(此接口是 Java Collections Framework 的成员)了。该接口包含int compare(Object o1,Object o2)方法,用于比较两个对象的大小,比较结果和compareTo方法一致。

示例:

import java.util.Set;
import java.util.TreeSet;

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

		//构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
		Set set = new TreeSet();

		set.add(17);
		set.add(-2);
		set.add(95);
		set.add(4);
		TreeSet s   = (TreeSet)set;//set不能直接调用Comparator()方法,必须要先把set强转型为TreeSet类型
		/*
		 *  Comparator<? super E> comparator()
          返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null。
		 * */
		System.out.println(s.comparator());
		System.out.println(s.first());
		System.out.println(s.last());
		/** 这里上下都是treeset类中的方法
		 *  SortedSet<E> headSet(E toElement)
          			返回此 set 的部分视图,其元素严格小于 toElement。
		 */
		System.out.println(s);
		System.out.println(s.headSet(17));
		System.out.println(s.tailSet(4));
	}
}

上述程序中的方法详细参见TreeSet类

程序运行结果:

-2

95

[-2, 4, 17, 95]

[-2, 4]

[4, 17, 95]

要实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象(TreeSet(Comparator comparator) ),该对象里负责集合元素的排序逻辑

示例:

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

/**
 * 根据人的年龄来排序,降序
 *
 */
class Person2 {
	private Integer age;

	public Person2(int age) {
		this.age = age;
	}

	public Integer getAge() {
		return age;
	}

	public String toString() {
		return this.age + "";
	}
}

	class MyComparator implements Comparator{
		//在这里写比较规则
		public int compare(Object o1, Object o2) {
			Person2 p1 = (Person2) o1;
			Person2 p2 = (Person2) o2;

			if(p1.getAge() > p2.getAge()){
				return -1;
			}else if(p1.getAge() < p2.getAge()){
				return 1;
			}
			return 0;
		}

	}
public class ComparatorDemo {
	public static void main(String[] args) {
		Set set = new TreeSet(new Comparator() {//new TreeSet(new MyComparator());
			//在这里写比较规则,用的是匿名内部类
			public int compare(Object o1, Object o2) {
				Person2 p1 = (Person2) o1;
				Person2 p2 = (Person2) o2;

				if(p1.getAge() > p2.getAge()){
					return -1;
				}else if(p1.getAge() < p2.getAge()){
					return 1;
				}
				return 0;
			}
		});
		set.add(new Person2(23));
		set.add(new Person2(13));
		set.add(new Person2(03));
		set.add(new Person2(43));
		set.add(new Person2(93));
		System.out.println(set);
	}
}

未完待续......

注:本文是参考众多资料,感谢前辈的支持。

时间: 2024-08-03 06:15:28

java集合框架详解(二)、set接口的相关文章

另外几种Java集合框架详解续

另外几种Java集合框架详解续 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs fastutil库优于Trove库的地方:Trove库已经三年未更新了,属于非活跃的开源项目:而fastutil一直在更新.fastutil有更丰富的特性,支持超大的集合(大于2^32,即4GB的集合),文档也更丰富. fastutil是一个开源的Java集合框架的扩展,它继承了Java Collection Framework,提供了数

java集合框架详解

前言: 数据结构对程序设计有着深远的影响,在面向过程的C语言中,数据库结构用struct来描述,而在面向对象的编程中,数据结构是用类来描述的,并且包含有对该数据结构操作的方法. 在Java语言中,Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类).所有抽象出来的数据结构和操作(算法)统称为Java集合框架(JavaCollectionFramework). Java程序员在具体应用时,不必考虑数据结构和算法实现细节,只需要用这些类创建出来一些对象,然后直接应

java集合框架详解(一)

1.为什么有java集合框架 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的接口.类来实现基本的数据结构.这些类均在java.util包中.在Java语言中,Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类).所有抽象出来的数据结构和操作(算法)统称为Java集合框架(Java Collection Framework). Java程序员在具体应用时,不必考虑数据结构和算法实现细节,只需要用这些类创建出来一些对

Java集合框架详解(全)

一.Java集合框架概述 集合可以看作是一种容器,用来存储对象信息.所有集合类都位于java.util包下,但支持多线程的集合类位于java.util.concurrent包下. 数组与集合的区别如下: 1)数组长度不可变化而且无法保存具有映射关系的数据:集合类用于保存数量不确定的数据,以及保存具有映射关系的数据. 2)数组元素既可以是基本类型的值,也可以是对象:集合只能保存对象. Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:Li

Java—集合框架 Collections.sort()、Comparable接口和Comparator接口

Collentions工具类--java.util.Collections Collentions是Java集合框架中,用来操作集合对象的工具类,也是Java集合框架的成员,与List.Map和Set是并列的. Collections.sort() 排序方法,实现对List对象中的元素进行排序. package com.test.collection; import java.util.ArrayList; import java.util.Collections; import java.ut

Java基础之集合框架详解(一)

集合框架的相关定义 集合:即存储对象的容器. 框架:即类库的集合. 集合框架:即用来表示和操作集合的统一框架,由相关的接口.实现类和一些帮助程序员完成编程的算法. 集合的作用: 在类的内部,对数据进行组织 简单而快速的搜索大数量的条目 有的集合接口,提供了一系列排列有序的元素,并且可以在序列中间快速的插入或者删除有关元素 有的集合接口,提供了映射关系,可以通过关键字(key)去快速查找到对应的唯一对象,而这个关键字可以是任意类型 集合与数组的区别: 集合长度是可变的,数组长度是固定不变的 集合中

菜鸟学php扩展 之 自动生成的扩展框架详解(二)

前言 上一文:菜鸟学php扩展 之 hello world(一),不问所以然的,强行与php扩展say hello了.对于ext_skel自动生成的框架,将在本文进行详解,当作备忘录. 正文 ext_skel的用法 ./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]] [--skel=dir] [--full-xml] [--no-help] --extname=module module is the

Java集合类型详解

这篇文章总结了所有的Java集合(Collection).主要介绍各个集合的特性和用途,以及在不同的集合类型之间转换的方式. Arrays Array是Java特有的数组.在你知道所要处理数据元素个数的情况下非常好用.java.util.Arrays 包含了许多处理数据的实用方法: Arrays.asList:可以从 Array 转换成 List.可以作为其他集合类型构造器的参数. Arrays.binarySearch:在一个已排序的或者其中一段中快速查找. Arrays.copyOf:如果你

Java基础之集合框架详解(三)Set篇

相关定义 作为Collection接口的重要子接口,Set接口是一个不包含重复元素,且元素排列无序的集合,也被称为集. 注意:不包含重复元素的含义,更确切的讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素. Set接口的方法和Collection接口的方法大体相同,也就是说Set接口不像List接口那样可以通过索引或去元素,只能通过Iterator()方法获取迭代器进行迭代. Set中的add方法不同于Collection接口,有一个