详解 Set接口
(请关注 本人“集合”总集篇博文——《详解 Collection接口》)
在Collection接口的子接口中,最重要的,也是最常见的两个—— List接口 和 Set接口。
那么,为什么有了 List接口这么方便的接口,还要讲解Set接口呢?
在本人博文——《详解 List接口》中就讲过:
List接口中可以存储相同的元素,在特定情况下,我们要进行处理的数据不能存在相同项(举个十分常见的例子:我国人民的身份证号),那么,我们就不能再用List进行存储了,为了防止重复数据的录入。
所以,就有了本人这篇博文的主题——Set接口
那么,话不多说了,开始我们这篇博文的讲解吧:
Set接口:
定义:
一个不包含重复元素的 collection。即 最多包含一个 null 元素。
(对其所包含的元素有所限制,例如:不允许存储null)
方法:
- boolean add(E e)
如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。- boolean addAll(Collection<? extends E> c)
如果 set 中没有指定 collection 中的所有元素,则将其添加到此 set 中(可选操作)。- void clear()
移除此 set 中的所有元素(可选操作)。- boolean contains(Object o)
如果 set 包含指定的元素,则返回 true。- boolean containsAll(Collection<?> c)
如果此 set 包含指定 collection 的所有元素,则返回 true。- boolean equals(Object o)
比较指定对象与此 set 的相等性。- int hashCode()
返回 set 的哈希码值。- boolean isEmpty()
如果 set 不包含元素,则返回 true。- Iterator iterator()
返回在此 set 中的元素上进行迭代的迭代器。- boolean remove(Object o)
如果 set 中存在指定的元素,则将其移除(可选操作)。- boolean removeAll(Collection<?> c)
移除 set 中那些包含在指定 collection 中的元素(可选操作)。- boolean retainAll(Collection<?> c)
仅保留 set 中那些包含在指定 collection 中的元素(可选操作)。- int size()
返回 set 中的元素数(其容量)。- Object[] toArray()
返回一个包含 set 中所有元素的数组。- < T > T[] toArray(T[] a)
返回一个包含此 set 中所有元素的数组;
返回数组的运行时类型是指定数组的类型
我们可以看到,Set接口 与 本人上一篇博文中讲解的 List接口的方法是大致相同的。
同样的,Set接口也有三个很常用的实现类——HashSet、LinkedHashSet 和 TreeSet。
那么,现在本人来对这三个类进行一下讲解吧:
HashSet:
特点:
- HashSet 底层数据结构是哈希表.
- HashSet 是 线程不安全的 (即: 效率高的)
- 集合元素可以是 null
- 元素(由底层数据结构决定):
无序(存储顺序不一致),
且唯一(存储的元素不重复)
在这里,可能会有同学有疑问了——哈希表是什么呢?
那么,本人在这里稍作解释:
哈希表(HashMap):
- 是一个元素为链表的数组(JDK1.7之前)
数组 + 链表 + 红黑树 的结构 (JDK1.8 之后)- 综合了数组和链表的优点
(至于“哈希”, 也就是“散列”的数据结构
在本篇博文中,本人不对这个算法进行讲解,仅讲解用了这个算法后
如需详解,请关注本人之后的博文——《HashMap 源码解析》)
但是,本人要强调的一点是:
我们用HashSet去存储新数据时,是按照如下步骤的:
- 调用该对象的hashCode() 方法来得到该对象的 hashCode 值
- 根据 hashCode 值决定该对象在 HashSet 中的存储位置
- 若存在 hashCode 值相等 且 在该 HashSet中已经存在的 元素时,
- 调用 这两个对象的 equals() 方法:
若equals()的返回值 相等,则判定这两个元素相等,不再录入新数据;
若equals()的返回值 不想等,则在 旧元素位置所在数组的位置 下 链接新元素(当链接的元素足够时,还要用到红黑树的数据结构)
现在,本人来通过一个例子来证明下上面的说法:
首先是只存储了 姓名 和 年龄 的People类:
package aboutSet;
public class People {
private String name;
private int age;
public People() {
}
public People(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;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
return true;
}
@Override
public int hashCode() {
return 0;
}
}
本人刻意让hashCode()和equals() 返回一个定值,
按照上面的说法,我们在测试类中所建立的HashSet容器,只能录入第一个人的信息。
那么,现在本人来给出一个测试类,来验证下上面的说法是否正确:
package aboutSet;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
People s1 = new People("张无忌", 19);
People s2 = new People("王祖贤", 23);
People s3 = new People("张学友", 20);
People s4 = new People("周润发", 20);
HashSet<People> Peoples = new HashSet<>();
Peoples.add(s1);
Peoples.add(s2);
Peoples.add(s3);
Peoples.add(s4);
for (People people : Peoples) {
System.out.println(people);
}
}
}
那么,现在本人来展示下运行结果:
可以清晰地看到,本人的说法毫无问题!
现在,本人将hashCode()和equals() 方法恰当地重写:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
People student = (People) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
并将测试类中给HashSet录入数据的代码改为如下:
People s1 = new People("张无忌", 19);
People s2 = new People("王祖贤", 23);
People s3 = new People("张无忌", 23);
People s4 = new People("张学友", 20);
People s5 = new People("张无忌", 20);
HashSet<People> Peoples = new HashSet<>();
Peoples.add(s1);
Peoples.add(s2);
Peoples.add(s3);
Peoples.add(s4);
Peoples.add(s5);
现在,我们再来看一下运行结果:
我们可以看到,由于我们重写了hashCode()和equals()方法,导致两者的返回值,
除了第一个和第五个意外,都不一样。
所以最终的输出结果只有四条,且都不一样,这就说明了:
HashSet存储数据的判断标准,由hashCode()和equals()这两个方法决定
那么,现在,本人再来给出一个例子:
package aboutSet;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
Integer s1 = new Integer(19);
Integer s2 = new Integer(23);
Integer s3 = new Integer(3);
Integer s4 = new Integer(0);
Integer s5 = new Integer(19);
HashSet<Integer> Integers = new HashSet<>();
Integers.add(s1);
Integers.add(s2);
Integers.add(s3);
Integers.add(s4);
Integers.add(s5);
for (Integer Integer : Integers) {
System.out.println(Integer);
}
}
}
可以看到,本人并没有重写hashCode()和equals()方法,而且我们的Integer 类型的五个对象,全都是通过new得到的,但是,让我们来看看输出结果:
可以看到,地址值不同的 Integer类型的五个对象,有一个却因为值相同,没有被录入
这又是为什么呢?难道本人上面的说法错了吗?
答曰:非也!这是因为:有些工具类(包括Integer类型在内),都重写了hashCode()和equals()方法,不需要我们去重写。
那么,在看完上面的结论和例子之后,本人再来提出一条结论:
我们重写hashCode()方法,是为了减少“哈希碰撞次数”(相关知识点将在本人《HashMap 源码解析》博文中进行讲解),在这里我们可以认为是 调用equals()方法的次数
那么,有关HashSet的基本知识点就讲解完毕了,下面本人来对本篇博文的第二个知识点——LinkedHashSet做一下简介:
LinkedHashSet:
特点:
- LinkedHashSet底层数据结构是 链表 和 哈希表.
- LinkedHashSet 是 线程不安全的 (即: 效率高的)
- 集合元素可以是 null
- 元素(由底层数据结构决定):
有序(链表结构保证),
且唯一(哈希表结构保证)
至于这个类,本人觉得没什么好多说的,除了有序外,其他特征几乎和HashSet一模一样。
现在,本人还是给出一个例子,来验证下上面的说法:
本人现在对上面的代码中的测试类修改如下:
package aboutSet;
import java.util.LinkedHashSet;
public class Test {
public static void main(String[] args) {
Integer s1 = new Integer(19);
Integer s2 = new Integer(23);
Integer s3 = new Integer(3);
Integer s4 = new Integer(0);
Integer s5 = new Integer(19);
LinkedHashSet<Integer> Integers = new LinkedHashSet();
Integers.add(s1);
Integers.add(s2);
Integers.add(s3);
Integers.add(s4);
Integers.add(s5);
for (Integer Integer : Integers) {
System.out.println(Integer);
}
}
}
那么,现在我们来看一下运行结果:
可以看到,上面的讲解没有问题。
那么,就开始这篇博文的最后一个知识点——TreeSet吧:
TreeSet:
特点:
- 元素唯一(二叉树存储结构 及 compareTo()方法 保证)
- 并且可以对元素进行排序 :
- 自然排序
- 使用比较器排序
那么,现在,本人就来对这两种排序方法进行一下讲解:
首先是自然排序:
自然排序:
条件:
- 表示这个元素的类 必须实现Comparable接口 (否则无法进行自然排序)
- 重写Comparable接口 中的compareTo()方法,根据此方法返回的正、负、0来决定元素的排列顺序:
若返回0:则不再录入新的元素;
若返回-1:则将新的元素放在正在比较的元素的左边;
若返回1:则将新的元素放在正在比较的元素的右边- TreeSet对象采用空参构造
举个例子:
现给出如下几个数:
20、18、23、22、17、24、19、18、24
那么, 排列顺序就由下图所示:
而当我们读取的时候,会按照本人在《数据结构与算法》专栏中的《二叉树的遍历 详解及实现》所讲的中根序(又称“中序”)一样。
最终的输出结果会是:17、18、19、20、22、23、24
那么,本人还是先对前一篇博文的测试类做下改动,来验证一下上面的预测:
package aboutSet;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
Integer s1 = new Integer(20);
Integer s2 = new Integer(18);
Integer s3 = new Integer(23);
Integer s4 = new Integer(22);
Integer s5 = new Integer(17);
Integer s6 = new Integer(24);
Integer s7 = new Integer(19);
Integer s8 = new Integer(18);
Integer s9 = new Integer(24);
TreeSet<Integer> Integers = new TreeSet();
Integers.add(s1);
Integers.add(s2);
Integers.add(s3);
Integers.add(s4);
Integers.add(s5);
Integers.add(s6);
Integers.add(s7);
Integers.add(s8);
Integers.add(s9);
for (Integer Integer : Integers) {
System.out.println(Integer);
}
}
}
那么,让我们来看一下运行结果吧:
可以看到,本人的预测十分准确!
那么,可能就有同学有疑问了:
为什么没有达到那两个条件就可以自然排序呢?
答曰:没错!Integer这个类内部实现了Comparable接口,并且重写了compareTo()方法。
本人现在来展示一下Integer的相关源码:
由此可见,本人的讲解没有错误!
那么,现在本人来展示下自己该如何使用自然排序:
首先本人给出一个TreeSet所存的元素的类:
package aboutSet;
import java.util.Objects;
public class People implements Comparable<People>{
private String name;
private int age;
public People() {
}
public People(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(People people) {
//假设根据学生的年龄大小来排序
int num = this.age - people.age;
//但是年龄一样并不严谨,我们还要根据姓名来比较
int num2 = num==0 ? this.name.compareTo(people.name) : num;
return num2;
}
}
现在,本人给出一个测试类:
package aboutSet;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<People> treeSet = new TreeSet<>();
treeSet.add(new People("张星彩", 23));
treeSet.add(new People("关银屏", 23));
treeSet.add(new People("王富贵", 23));
treeSet.add(new People("姬无力", 20));
treeSet.add(new People("奥瑞利安索尔", 30));
treeSet.add(new People("jo太郎", 23));
for (People People : treeSet) {
System.out.println(People);
}
}
}
可以看到,一切如本人在代码中的解释所料!
那么,接下来,本人来展示下 使用比较器排序:
使用比较器排序:
条件:
- 采用有参构造,且在创建TreeSet对象时,需要通过参数传入一个Comparetor 比较器
现在,本人来展示下如何 使用比较器排序:
第一种手段——构造一个比较器类:
首先是存储元素信息的类:
package aboutSet;
import java.util.Objects;
public class People {
private String name;
private int age;
public People() {
}
public People(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
那么,现在本人来给出一个"按照元素姓名长度进行比较"比较器:
package aboutSet;
import java.util.Comparator;
public class TestComparator implements Comparator<People>{
public TestComparator() {
}
@Override
public int compare(People people1, People people2) {
//按照年龄大小排序
int num = people1.getAge() - people2.getAge();
int num2=num==0?people1.getName().compareTo(people2.getName()):num;
return num2;
}
}
现在,本人来给出一个测试类:
package aboutSet;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<People> treeSet = new TreeSet<>(new TestComparator());
treeSet.add(new People("张星彩", 23));
treeSet.add(new People("关凤", 23));
treeSet.add(new People("王权富贵", 23));
treeSet.add(new People("姬无力", 20));
treeSet.add(new People("奥瑞利安索尔", 30));
treeSet.add(new People("空条jo太郎", 23));
for (People People : treeSet) {
System.out.println(People);
}
}
}
那么,让我们来看一下运行结果:
第二种手段——通过 匿名内部类 传递:
People类不做改变,本人仅改变测试类:
package aboutSet;
import java.util.Comparator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<People> treeSet = new TreeSet<>(new Comparator<People>() {
@Override
public int compare(People people1, People people2) {
//按照年龄大小排序
int num = people1.getAge() - people2.getAge();
int num2=num==0?people1.getName().compareTo(people2.getName()):num;
return num2;
}
});
treeSet.add(new People("张星彩", 23));
treeSet.add(new People("关凤", 23));
treeSet.add(new People("王权富贵", 23));
treeSet.add(new People("姬无力", 20));
treeSet.add(new People("奥瑞利安索尔", 30));
treeSet.add(new People("空条jo太郎", 23));
for (People People : treeSet) {
System.out.println(People);
}
}
}
让我们来看一下运行结果:
可以看到,结果依旧正确。
在这里,本人来对这两种 比较器排序 的应用场景来做一下说明:
一般地:
若是 只用一次该比较器,则使用匿名内部类传递
若是 反复多次使用该比较器,则通过一个比较器类传递
(Collection链接:https:////www.cnblogs.com/codderYouzg/p/12416566.html)
(集合总集篇链接:https://www.cnblogs.com/codderYouzg/p/12416560.html)
详解 Set接口的更多相关文章
- 详解EBS接口开发之库存事务处理采购接收--补充
除了可以用 详解EBS接口开发之库存事务处理采购接收的方法还可以用一下方法,不同之处在于带有批次和序列控制的时候实现方式不同 The script will load records into ...
- 详解EBS接口开发之采购申请导入
更多内容可以参考我的博客 详解EBS接口开发之采购订单导入 http://blog.csdn.net/cai_xingyun/article/details/17114697 /*+++++++ ...
- 详解EBS接口开发之库存事务处理批次更新
库存事务处理批次有时候出现导入错误需要更新可使用次程序更新,批次导入可参考博客 详解EBS接口开发之库存事务处理-物料批次导入 http://blog.csdn.net/cai_xingyun/art ...
- 供应商API补充(详解EBS接口开发之供应商导入)(转)
原文地址 供应商导入的API补充(详解EBS接口开发之供应商导入) --供应商 --创建 AP_VENDOR_PUB_PKG.Create_Vendor ( p_api_version IN NUM ...
- 集合类 Contains 方法 深入详解 与接口的实例
.Net 相等性:集合类 Contains 方法 深入详解 http://www.cnblogs.com/ldp615/archive/2009/09/05/1560791.html 1.接口的概念及 ...
- 详解 List接口
本篇博文所讲解的这两个类,都是泛型类(关于泛型,本人在之前的博文中提到过),我们在学习C语言时,对于数据的存储,用的差不多都是数组和链表. 但是,在Java中,链表就相对地失去了它的存在价值,因为Ja ...
- 详解EBS接口开发之应收款处理
参考实例参考:杜春阳 R12应收模块收款API研究 (一)应收款常用标准表简介 1.1 常用标准表 如下表中列出了与应收款处理相关的表和说明: 表名 说明 其他信息 AR_BATCHES_ALL ...
- 详解EBS接口开发之库存事务处理采购接收和退货
(一)接收&退货常用标准表简介 1.1 常用标准表 如下表中列出了与采购接收&退货导入相关的表和说明: 表名 说明 其他信息 RCV_TRANSACTIONS 采购接收事务表 事务 ...
- 详解EBS接口开发之供应商导入
(一)供应商常用标准表简介 1.1 常用标准表 如下表中列出了与供应商相关的表和说明: 表名 说明 其他信息 ap_suppliers 供应商头表 供应商的头信息如:供应商名.供应商编码.税号等 ...
随机推荐
- 微信APP支付-java后台实现
不说废话,直接上代码 先是工具类(注意签名时要排序): import java.io.BufferedReader; import java.io.ByteArrayInputStream; impo ...
- pywin32解析office文档
from win32com.client import Dispatch xlApp = Dispatch("Excel.Application") xlApp.Visible = ...
- html之锚点
我命由我不由天 豆瓣:哪吒之魔童降世 哪吒之魔童降世简述 更新描述或海报 导演: 饺子 编剧: 饺子 / 易巧 / 魏芸芸 主演: 吕艳婷 / 囧森瑟夫 / 瀚墨 / 陈浩 / 绿绮 / 更多... ...
- Ali_Cloud++:安装 RabbitMQ安装及环境配置
注意事项:rabbitMA版本和erlang并不是同步更新的,会出现版本不匹配,安装不了. 两都版本对应 参考官网文档 其它下载地址 1):Erlang安装 (因为是erlant语言编写的, ...
- POJ旅行商问题——解题报告
旅行商问题 总时间限制: 1000ms 内存限制: 65536kB 描述 某国家有n(1<=n<=10)座城市,给定任意两座城市间距离(不超过1000的非负整数).一个旅行商人希望访问每座 ...
- Java多线程问题40个
1.多线程有什么用? 一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡.所谓”知其然知其所以然”,”会用”只是”知其然”,”为什么用”才是”知其所以然 ...
- 读者来信 | 刚搭完HBase集群,Phoenix一启动,HBase就全崩了,是什么原因?(已解决)
前言:之前有朋友加好友与我探讨一些问题,我觉得这些问题倒挺有价值的:于是就想在本公众号开设一个问答专栏,方便技术交流与分享,专栏名就定为:<读者来信>.如遇到本人能力有限难以解决的问题,我 ...
- Vue的父子组件v-model双向绑定,父组件修改子组件中绑定的v-model属性
先来看下实现的效果,父组件中有个文本框,在点击下面按钮时弹出抽屉,抽屉里也有个文本框,文本框里的初始值要和父组件的文本框同步,并且修改抽屉里的文本框值时 父组件里的文本框值也要跟着改变 网上有大概三种 ...
- Sql练习201908200918
表结构: 昨天遇到一个笔试题,求一个聚合函数小于等于1000的值,并将编号同时输出. sql server: select nid,SUM(amount) amountSum from orders_ ...
- 【WPF学习】第六十四章 构建基本的用户控件
创建一个简单用户控件是开始自定义控件的好方法.本章主要介绍创建一个基本的颜色拾取器.接下来分析如何将这个控件分解成功能更强大的基于模板的控件. 创建基本的颜色拾取器很容易.然而,创建自定义颜色拾取器仍 ...