Set接口

Set接口

java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。与 List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

Set集合取出元素的方式可以采用:迭代器、增强for。

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。

Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法使用循环进行输出。 那么在此接口中有两个常用的子类:HashSet、TreeSet

HashSet集合(重点

HashSet集合介绍:

java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的 (即存取顺序不一致)。java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持,由于我们暂时还未学习,先做了解。

HashSet 属于散列的存放类集,里面的内容是无序存放的。

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证 元素唯一性的方式依赖于: hashCode 与 equals 方法。

public class HashSetDemo {
	public static void main(String[] args) {
		//创建 Set集合
		HashSet<String> set = new HashSet<String>();
		//添加元素
		set.add(new String("123"));
		set.add("123");
		set.add("123");
		set.add("321");
		//遍历
		for (String name : set) {
			System.out.println(name);
		}
	}
}

输出结果如下,说明Set集合中不能存储重复元素:

123
321

​ 使用 HashSet 实例化的 Set 接口实例,本身属于无序的存放。 那么,现在思考一下?能不能通过循环的方式将 Set 接口中的内容输出呢? 是可以实现的,因为在 Collection 接口中定义了将集合变为对象数组进行输出。

public class HashSetDemo02 {
	public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        Object obj[] = all.toArray(); // 将集合变为对象数组
        for (int x = 0; x < obj.length; x++) {
			System.out.print(obj[x] + "、");
		}
	}
}

但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类 型变为指定的数组。 所以只能使用以下的方法: T[] toArray(T[] a)

public class HashSetDemo03 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组
        for (int x = 0; x < str.length; x++) {
        	System.out.print(str[x] + "、");
    	}
    }
}

HashSet集合存储数据的结构(哈希表)

什么是哈希表呢?

JDK1.8 之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一 个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率 较低。而 JDK1.8 中,哈希表存储采用 数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的

存储流程图:
在这里插入图片描述

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式

HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

LinkedHashSet

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢? 在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构

排序的子类:TreeSet(重点

与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:

public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable

下面通过代码来观察其是如何进行排序的。

public class TreeSetDemo01 {
    public static void main(String[] args) {
        Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\
        all.add("D");
        all.add("X");
        all.add("A");
        System.out.println(all);
    }
}

虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。

排序的说明(重点)

既然 Set 接口的 TreeSet 类本身是允许排序,那么现在自定义一个类是否可以进行对象的排序呢?

定义 Person 类:

public class Person {
    private String name;
    private int age;
    public Person() {
    }
    public Person(String name, int age) {
    	this.name = name;
    	this.age = 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;
    }
    public String toString() {
    	return "姓名:" + this.name + ",年龄:" + this.age;
    }
}

下面定义一个 TreeSet 集合,向里面增加若干个 Person 对象。

public class TreeSetPersonDemo01 {
    public static void main(String[] args) {
        Set<Person> all = new TreeSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10))
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

执行以上的操作代码之后,发现出现了如下的错误提示:

Exception in thread "main" java.lang.ClassCastException:
org.lamp.listdemo.treesetdemo02.Person cannot be cast to java.lang.Comparable
    at java.util.TreeMap.put(Unknown Source)
    at java.util.TreeSet.add(Unknown Source)
    at
org.lamp.listdemo.treesetdemo02.TreeSetPersonDemo01.main(TreeSetPersonDemo01.java:11)

此时的提示是:Person 类不能向 Comparable 接口转型的问题

所以,证明,如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    public int compareTo(Person per) {
    	if (this.age > per.age) {
    		return 1;
    	} else if (this.age < per.age) {
        	return -1;
        } else {
        	return 0;
    	}
    }
    public Person() {
    }
    public Person(String name, int age) {
    	this.name = name;
    	this.age = 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;
    }
    public String toString() {
    	return "姓名:" + this.name + ",年龄:" + this.age;
    }
}

那么此时再次使用之前的代码运行程序。程序的执行结果如下:

[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12,

​ 从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。

public int compareTo(Person per) {
    if (this.age > per.age) {
    	return 1;
    } else if (this.age < per.age) {
    	return -1;
    } else {
    	return this.name.compareTo(per.name);
    }
}

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

关于重复元素的说明(重点)

之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,那么证明 如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。

public class HashSetPersonDemo01 {
    public static void main(String[] args) {
        Set<Person> all = new HashSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable 接口间接完成的。

如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。

从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码

  • 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。

所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。

public boolean equals(Object obj) {
    if (this == obj) {
    	return true;
    }
    if (!(obj instanceof Person)) {
    	return false;
    }
    Person per = (Person) obj;
    if (per.name.equals(this.name) && per.age == this.age) {
        return true;
    } else {
        return false;
    }
}
public int hashCode() {
    return this.name.hashCode() * this.age;
}

发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠 hashCode()和 equals()方法共同完成。

小结:

关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。

不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值