您的位置 首页 java

初识java—(四十一)Set集合

Set集合是 Collection 集合的子类,与Collection基本上完全一样,它没有提供额外的方法,只是在行为上略有不同。

Set集合不允许包含相同的元素,如果把两个相同的元素加入到同一个Set集合中去,则添加操作失败,add方法返回false,且新元素不会被加入。

Set判断两个对象相同不是使用==运算符,而是使用equals方法。也就是说,只要两个对象equals方法比较返回true,Set就不会接受这两个对象;反之,则可以。

注意:Set集合的元素是无序的,但作为集合来说,它有自己的存储顺序,有可能会出现你添加的顺序正好和它存储的顺序一样的情况。

举例1:

public static void main(String[] args) throws Exception {

Set set = new Hash Set ();

set.add(“zhangsan”) ;

set.add(“zhangsan”) ;

System. out .println(set.size());

set.add( new String(“zhangsan”)) ;

set.add( new String(“zhangsan”)) ;

System. out .println(set.size());

}

因为: String 对象使用equals比较是相同所以只添加了一个。

public static void main(String[] args) throws Exception{

Car car1 = new Car();

Car car2 = new Car();

Set set = new HashSet ();

set.add(car1) ;

set.add(car2) ;

System. out .println(set.size()); //2

}

car1与car2分别指向了两个不同的对象,因此使用equals比较时返回false,所以set添加了两个对象。

具体来看下HashSet、TreeSet和EnumSet三个实现类。

8.3.1 HashSet类

HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个实现类。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。(感兴趣可以了解Hash算法)

一个对象的 HashCode 就是一个简单的Hash算法的实现,虽然它和那些真正的复杂的Hash算法相比还不能叫真正的算法,但如何实现它,不仅仅是 程序员 的编程水平问题,而是关系到你的对象在存取时性能的非常重要的问题.有可能,不同的HashCode可能会使你的对象存取产生,成百上千倍的性能差别。

默认的实现是将对象内部地址转化为整数作为HashCode,这当然能保证每个对象具有不同的HasCode,因为不同的对象内部地址肯定不同,但 Java 语言并不能让程序员获取对象内部地址,所以,让每个对象产生不同的HashCode有着很多可研究的技术。

HashSet具有以下特点:

Ø 不能保证元素的排列顺序,排列顺序可能与添加顺序不同。

Ø HashSet不是同步的,如果多个 线程 同时访问一个HashSet时,假设有一个或多个线程同时修改了HashSet集合时,则必须通过代码块来保证其同步。

Ø 集合元素可以是null。

当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的值,然后根据该HashCode()值决定该对象在HashSet中的存储位置。如果两个元素通过equals()方法比较返回true,而他们的hashCode()方法返回值不同,HashSet会将他们存储在不同的位置,依然添加成功。这就与Set集合的规则有些出入了。

public class TestOne {

private int num = 1;

public TestOne( int num){

this .num = num;

}

public Boolean equals(Object obj) {

return true ;

}

public int hashCode() {

return this .num;

}

}

public static void main(String[] args) throws Exception{

TestOne one = new TestOne(1);

TestOne two = new TestOne(2);

System. out .println(one.equals(two));

Set set = new HashSet ();

set.add(one) ;

set.add(two) ;

System. out .println(set.size()); // 2

}

注意:如果通过equals方法比较返回true,同时根据hashCode()方法获取的返回值也相同,则只能存储一个对象。

当把一个对象放入到HashSet中时,如果重写了这个对象的equals方法,那么也必须重写这个对象的hashCode方法。其规则就是如果equals方法返回true,那么这两个对象的hashCode的值也应该相同。

如果两个对象通过equals方法比较返回false,而hashCode值返回一样,这就有点违背了HashSet的设计规则,本来通过Hash算法我们可以计算对象的存储位置,现在却成了在同一个位置上存储了两个对象。从而减低了HashSet快速查找对象的功能。

问题1:下面我们来看一下源码中的HashSet的add()方法是怎么操作的。

我们会看到首先看哈希值,如果相同再去比较地址值或者是比较equals()方法,如果不同,直接添加到集合当中去。

问题2:接下来看一下往 Set集合中添加学生对象的情况,我要求学生对象的属性相同也不能添加进去要怎么办呢?添加多个对象的时候,它们首先调用的是hashCode方法,发现它们不一样,不走equals方法,直接就添加进去了。而我们现在重写一下这两个方法就可以了。

首先重写它的hashCode()方法:

public int hashCode(){

return 1;//每次都判断相同,然后让它们再通过equals方法去比较内容是否相同。

}

然后再重写它的equals方法:

public Boolean equals(Object obj){

if(this = obj){//提高效率

return true;

}

if (!(obj instanceof Student)){

return false;

}

Student s = (Student)obj;

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

}

哈希表 :是一个元素为 链表 的数组。它整合了数组与链表的好处。

底层存储,画图的方式显示。

现在,我们可以解决问题了,但是这样工作效率不同,我们可以优化一下,让对象的哈希值尽可能的不同,而对象的哈希值与对象的成员变量值相关,所以,我们在重写hashCode()方法的时候,可以把对象的成员变量作一个相加运算,如果是基本类型,就直接加值,如果是引用类型,就加哈希值。

但是相加之后还可能会出现两个对象的值相加之后会相等,这时我们就可以对某个数值作一个乘法运算就可以了。

问题3:我们的hashCode()与equails方法是可以自动重写的。

8.3.2 LinkedHashSet类

LinkedHashSet是HashSet的子类,LinkedHashSet同样是根据元素的hashCode值来决定元素的存储位置,但与HashSet不同的是,LinkedHashSet在存储对象时同时使用链表维护了元素的次序,即:当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按照元素的添加顺序来访问集合里的元素。

举例1:

public static void main(String[] args) throws Exception{

Set set = new LinkedHashSet ();

set.add(“张三”) ;

set.add(“李四”) ;

set.add(“王五”) ;

System. out .println(set);

}

输出结果:[张三, 李四, 王五]

8.3.3 TreeSet类

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。与HashSet相比,TreeSet还提供了额外方法:

Ø Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator,如果TreeSet采用了自然排序,则返回null。

Ø Object first():返回集合中的第一个元素。

Ø Object last():返回集合中的最后一个元素。

Ø Object lower(Object obj):返回集合中位于指定元素之前的元素。

Ø Object higher(Object obj):返回集合中位于指定元素之后的元素。

Ø SortedSet subSet(fromElement,toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。

Ø SortedSet headSet(toElement):返回Set的子集,由小于toElement的元素组成。

Ø SortedSet tailSet(fromElement):返回Set的子集,由大于或等于fromElement的元素组成。

举例1:

public static void main(String[] args) throws Exception{

TreeSet set = new TreeSet ();

set.add(12) ;

set.add(3) ;

set.add(6) ;

set.add(8) ;

System. out .println(set);

System. out .println(set.first());//3

System. out .println(set.last()); //12

System. out .println( set.headSet(8) ); // 6 3

System. out .println( set.tailSet(6) ); //6 8 12

System. out .println( set.subSet(3, 8) ); // 3 6

}

HashSet集合采用hash算法来决定元素的存储位置,TreeSet采用 红黑树 的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序。

Ø 自然排序

TreeSet会调用集合元素的compareTo(Object obj):方法来比较元素之间的大小关系,然后将集合元素按 升序 排列,这种方式就是自然排序。

java 中提供了一个Comparable接口,此接口中定义了一个方法compareTo(Object obj),该方法返回一个整数值,实现了该接口的类的对象就可以比较大小。由于在向TreeSet集合中存放数据时,TreeSet会调用该元素对象的compareTo方法,因此往TreeSet集合中存放是元素对象必须实现了该方法。

大部分类在实现 compareTo 方法时,都需要将被比较对象obj强制转换成相同类型,因为只有相同类型的实例才能比较大小。因此TreeSet集合中存放的元素必须是同一类型的实例。否则将会抛出ClassCastException(强制类型转换异常)

举例1:

package com.langsin.test;

public class A {

}

package com.langsin.test;

public class B {

}

public static void main(String[] args) throws Exception{

TreeSet set = new TreeSet ();

set.add( new A()) ;

set.add( new B()) ;

System. out .println(set);

}

举例2:将A类与B继承接口Compareable,存储时不会报错

package com.langsin.test;

public class A implements Comparable {

public int compareTo(Object o) {

return 1;

}

}

package com.langsin.test;

public class B implements Comparable {

public int compareTo(Object o) {

return 1;

}

}

public static void main(String[] args) throws Exception{

TreeSet set = new TreeSet ();

set.add( new A()) ;

set.add( new B()) ;

System. out .println(set);

}

举例3:

package com.langsin.test;

public class Student implements Comparable<Student>{

public Student(String classNo){

this .classNo = classNo;

}

private String classNo= null ;

public int compareTo(Student st){

return this .classNo.compareTo(st.classNo);

}

public String getClassNo(){

return this .classNo;

}

}

public static void main(String[] args)

throws Exception{

Student st1 = new Student(“20140701”);

Student st2 = new Student(“20140702”);

Student st3 = new Student(“20140703”);

TreeSet<Student> set = new TreeSet<Student>();

set.add(st3);

set.add(st1);

set.add(st2);

System. out .println(set.first().getClassNo());

}

举例4.

// 7,9,10,13,17,23,34,34,43

public static void main(String[] args){

TreeSet ts = new TreeSet();

ts.add(7);

.

.

ts.add(43);

}

下面我们来看两个东西:

(1) TreeSet保证数据按序排序的源码。(只看图形化)

(2)TreeSet的数据存放与获取到底是什么样的,怎么会实现了一种排序呢?

TreeSet集合的底层是 二叉树 结构,它还是红黑树,是一种自平衡的二叉树。

那元素是如何存放在二叉树里边的呢,第一个元素存放作为根节点存储,第二个元素开始,每个元素与根节点进行比较,大了,放在右边,小了,放在左边,相等,不处理。取值的时候从根节点开始,根据左,中,右的原则依次取出元素即可。

Ø 定制排序

TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果实现定制排序,比如降序,需要通过Comparator接口来实现。在创建TreeSet实例的时候,不再使用TreeSet默认的比较,通过Comparator接口实现自己的 比较器 实例,将比较器的实例作为参数通过TreeSet的构造器传递给集合,那么在往集合元素中存放数据的时候就会按照我们的指定顺序进行排序。实现Comparator接口的int compare(T t1,T t2)方法, 此方法在往集合元素中添加元素对象时被调用,该方法返回一个int类型值,返回正整数,表示t1大于t2;返回负整数,表示t1小于t2;返回0表示,t1等于t2.

举例1:

public static void main(String[] args) throws Exception{

Comparator<Integer> comparator = new Comparator<Integer>(){

public int compare(Integer num1, Integer num2) {

if (num1>num2){

return 1;

} else if (num1<num2){

return -1;

} else {

return 0;

}

} //梅花8黑桃K红桃J黑桃5 方块5 黑桃红桃梅花方块

};

TreeSet set = new TreeSet(comparator) ;

set.add(1) ;

set.add(12) ;

set.add(5) ;

System. out .println(set);

}

8.3.4 Enum Set类

EnumSet类是枚举集合类,集合中存放的元素都必须是指定枚举类型的枚举值,该值在创建EnumSet时显示或隐式地指定。

EnumSet类没有提供公有的构造器来创建该类的实例,程序应该通过EnumSet提供是static方法来创建EnumSet对象。常用的方法如下:

Ø static EnumSet allOf(Class elementType):创建一个包含了枚举类中所有枚举值的EnumSet集合。

Ø static EnumSet complementOf(EnumSet s):创建一个其元素类型与指定的EnumSet里元素类型相同的EnumSet集合,新的EnumSet集合中包含了原EnumSet集合中所不包含的,剩下的所有枚举值。

Ø static EnumSet copy Of(Collection c):使用一个普通集合来创建一个EnumSet集合。普通集合Collection参数不能为空,里面必须含有元素对象。否则抛出java.lang.IllegalArgumentException(参数不合理异常)。

Ø static EnumSet copyOf(EnumSet s):创建一个与指定的EnumSet具有相同元素类型、相同集合元素的EnumSet集合。完整的copy一份。

Ø static EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet。

Ø static EnumSet range(E from,E to):创建一个包含了从from开始到to结束的范围内所有的 枚举值 集合。

举例1:

枚举类

package com.langsin.test;

public enum Planet {

MERCURY ,

VENUS ,

EARTH ,

MARS ,

JUPITER ,

SATURN ,

URANUS ,

NEPTUNE

}

测试类:

public static void main(String[] args) throws Exception{

EnumSet<Planet> set = EnumSet. allOf (Planet. class );

System. out .println(set.add(Planet. EARTH ));

System. out .println(set.size());

Collection<Planet> collection = new HashSet<Planet>();

collection.add(Planet. MARS );

EnumSet<Planet> set1 = EnumSet. copyOf (collection);

System. out .println(set1.size());

EnumSet<Planet> set2 = EnumSet. noneOf (Planet. class );

System. out .println(set2.size());

EnumSet<Planet> set3 = EnumSet. range (Planet. MERCURY , Planet. EARTH );

System. out .println(set3.size());

}

文章来源:智云一二三科技

文章标题:初识java—(四十一)Set集合

文章地址:https://www.zhihuclub.com/190046.shtml

关于作者: 智云科技

热门文章

网站地图