Java中HashSet的重复性与判等运算重载
本文地址:https://www.cnblogs.com/oberon-zjt0806/p/12367370.html
本文遵循CC BY-NC-SA 4.0协议,转载请注明出处。
特别说明:本文的基本语境是Java,如果需要C#版本请看这里
还有一个故事……(平行世界篇)
这是一个关于另外一个平行世界——Java中的相似的故事……
文艺复兴.jpg……
还有一个美丽的梦幻家园:java.util
在Java中,巧了,也有泛型的数据容器。不过,Java的容器和C#的组织方式有些不同,C#是单独开了一个System.Collections
及子命名空间专门用于给容器类使用,而Java则是把容器连同其他的工具类一起丢到了java.util
这一个大包中。
不过,容器的这部分内容似乎在Java里叫做JCF(Java Collections Framework)
而且,Java似乎不存在非泛型版本的容器,尽管据说SE 5之前的容器普遍存在类型安全性问题(当然已经是过去了……),此外,Java还提供了对应于一些容器的功能接口(而且是泛型接口),方便自定义容器类型,例如,List<E>
是列表容器的接口而不是泛型容器,其对应的泛型容器是ArrayList<E>
:
Pigeon p = new Pigeon("咕咕咕"); // class Pigeon extends Bird
Cuckoo c = new Cuckoo("子规"); // class Cuckoo extends Bird
List<Bird> birds = new List<Bird>() { { add(p); add(c); } }; // 错误,List是容器接口,不能直接实例化
ArrayList<Bird> flock = new ArrayList<Bird>() { { add(p); add(c); } }; // 正确,这是一个泛型为Bird的ArrayList容器
List<Bird> avians = new ArrayList<Bird>() { { add(p); add(c); } }; // 正确,ArrayList<E>实现了List<E>,可视为List<E>的多态
匿名内部类(AIC)
这个神奇的初始化写法在Java术语里叫做匿名内部类(AIC,Anonymous Inner Class),在Java中AIC是被广泛使用而且屡试不爽的,主要是用于简化Java代码。AIC的出现使得从一个抽象的接口或抽象类(无法实例化,不提供实现)快速重构一个简单具体类(可以实例化,具有实现)变得非常容易而无需另开文件去写类,而不会造成太大的性能影响(因为AIC是随用随丢的)。
不过AIC有个不算副作用的副作用,因为AIC往往需要实现(甚至可能是大量改写)接口或抽象类的方法,因此可能会在嵌套层数特别多的上下文中使得原本就比较混乱的局面更加混乱(特别是采用了不当的缩进策略的时候,因为AIC的写法本身在大多数情形下就包含了相当多的嵌套),导致代码可读性严重下降,看起来不是很直观,有碍观瞻。
此外,如果某个AIC频繁地出现,那么AIC就不那么适用了,这种情况下建议把当前的AIC改成一个具名的类。
并且还有一个善战的达拉崩巴:HashSet
更加巧合的是,在java.util
里也有一个HashSet<E>
,功能也是作为一个哈希集使用,也就是说它也满足如下两点:
- 元素是唯一的
- 元素是无序的
What a COINCI-DANCE~~
而且,也是要分两种情况,值类型下,只要两个值相等,那么第二个元素就不会被添加:
int i = 5;
int j = 5;
HashSet<int> integers = new HashSet<int>();
integers.add(i); // i被添加到integers中
integers.add(j); // 没有变化,integers中已经有5了
而对于引用类型来说,和C#类似,也采用引用一致性判断:
// 为了简单这里不封装了,直接上字段
class Student {
public int id;
public String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
public class Program {
public static void main(String[] args) {
Student s1 = new Student(1, "Tom");
Student s2 = new Student(2, "Jerry");
Student s3 = s1;
Student s4 = new Student(1,"Tom");
HashSet<Student> students = new HashSet<Student>();
students.add(s1); // s1被加入students中
students.add(s2); // s2被加入students中
students.add(s3); // 没有变化,s1已存在
students.add(s4); // s4被加入到students中,尽管s4和s1长得一样,但引用不一致
}
}
我甚至是差不多拿上篇文章中的代码,几乎没怎么改23333
但是,和上次一样的问题,尽管s4
和s1
引用不一致,但实际场合下,我们倾向于把它们当作同一个人,那么怎么办呢??
还有另外一个故事(不是虚假传说)
不是虚假传说-序言
嗯,这个不是虚假的故事,这就是正经的解决方案,放心大胆的读吧!!
还有一对涂满毒药的夺命双匕:equals和hashCode
当然,Java里所有对象都继承自java.lang.Object
即Object
,而Java对象也有两种相等判别方式:==
和Object.equals
。
而且,这俩判别方式一模一样,值类型下只要值相等就可以,而对于引用类型,==
判别的是引用一致性。
但是为什么这次标题里没有==的故事了??
一直就没有,那是你的错觉,上一篇的==
还是虚假的故事呢,而且原因也很简单:
Java里运算符不允许重载。
而且Object
里没有之前的ReferenceEquals
,所以==
就是引用一致性的兜底判别,没法重载的话那就免谈了,不过equals
是父类方法,当然是可以重载的。
那hashCode呢??
和隔壁的System.Object.GetHashCode()
类似地,这边也有一个java.lang.Object.hashCode()
,作用也是类似的,返回一个用作哈希值的数。
而且更加巧合的是,这里的Object.equals()
和hashCode()
也没什么关系,单独改写其中一个函数对另外一个函数也都没什么影响。
最最巧合的是,和隔壁一样,Java也建议equals
和hashCode
要改都改。
不过之前是因为非泛型容器(比如Hashtable
),而这次是真真正正的为了泛型容器。
而HashSet<E>
正是使用equals
和hashCode
作为双重判据,HashSet<E>
认为equals
返回true
,且两者hashCode
相等的时候,就认为是相同的元素而不被
那把骑士圣剑呢??
非常遗憾,这里没有那种东西,java.util
并没有提供类似于IEqualityComparer<T>
的东西,而HashSet<E>
也不提供getComparator()
这种方法……
java.util
只提供这个东西——interface Comparator<T>
,其作用和C#中的IComparer<T>
差不多,因为Java不让重载运算符,因此Comparator<T>
提供了compare
方法进行的大小比较,而且只是用于比较排序而已。
然后崩巴也准备开启营救公主的冒险
最后把程序改写成这个样子:
import java.util.HashSet;
class Student {
public int id;
public String name;
public Student(int id,String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return id == ((Student)obj).id && name.equals(((Student)obj).name);
}
@Override
public int hashCode() {
return id;
}
}
public class HSetTest {
public static void main(String[] args) {
Student s1 = new Student(1,"Tom");
Student s2 = s1;
Student s3 = new Student(1,"Tom");
@SuppressWarnings("serial")
HashSet<Student> students = new HashSet<Student>() {
{
add(s1); // s1被添加到students中
add(s2); // 没有变化,s1已存在
add(s3); // 没有变化,s3被认为和s1逻辑上相等
}
};
for(Student s : students) {
System.out.println(String.format("%d.%s",s.id,s.name));
}
}
}
输出结果:
1.Tom
Java中HashSet的重复性与判等运算重载的更多相关文章
- C#中HashSet的重复性与判等运算重载
目录 一个故事-- 一个繁荣的遥远国度:泛型容器 但是我也不确定容器里能放些什么东西啊 一个英勇的皇家骑士:HashSet 值类型的HashSet 引用类型的HashSet 另外一个--故--事?? ...
- Java中的二进制及基本的位运算
Java中的二进制及基本的位运算 二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二 ...
- Java中HashSet的解读
一. HashSet源代码 HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的 ...
- Java中HashSet和HashMap
Set中存储元素为什么不重复(即使hashCode相同)? HashSet中存放自定义类型元素时候,需要重写对象中的hashCode方法和equals方法, HashSet中存放自定义类型元素时候,需 ...
- Java中HashSet,HashMap和HashTable的区别
HashMap.HashSet.HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析: 在分析之前,先将其区别列于下面 1:HashSet底层采用的 ...
- java中HashSet详解(转)
HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSe ...
- java中HashSet详解
HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSe ...
- java集合(4)- java中HashSet详解
HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSe ...
- java中hashSet原理
转自: http://blog.csdn.net/guoweimelon/article/details/50804799 HashSet是JavaMap类型的集合类中最常使用的,本文基于Java1. ...
随机推荐
- Python基础————文件操作
文件操作 4.1 文件基本操作 obj = open('路径',mode='模式',encoding='编码') # 表示要干嘛 读 还是写 obj.write() #写什么内容 obj.read() ...
- 从0到1掌握某Json-TemplatesImpl链与ysoserial-jdk7u21的前因后果
本文首发于先知社区: https://xz.aliyun.com/t/7096 前言 作为一名安全研究人员(java安全菜鸡),知道拿到exp怎么打还不够,还得进一步分析exp构造原理与漏洞原理才行. ...
- CDH大数据平台搭建终极版
经过无数次的失败,终于将CDH安装到两台普通的笔记本电脑上,主要失败原因有以下几点: 不熟悉安装过程,官方给出的安装方法有三种,所以都尝试了一遍,浪费了大量时间,所以有时候方法多不见得是一件好事. 安 ...
- pywin32 获取 windows 的窗体内文本框的内容
用 spy++去确认找到了文本框的句柄了. 用函数 win32gui.SendMessage 获取不了文本框的文本内容,用 str 类型的参数接收获取的内容的话没有获取到东西,而用 PyBuffer ...
- spring.net 基础 1
Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序 1: 在2004年初,Martin Fowler曾经问他网站的读者:当我们谈到控制反转时,"问题是, ...
- Mayor's posters (线段树+离散化)
Mayor's posters Description The citizens of Bytetown, AB, could not stand that the candidates in the ...
- 剑指Offer对答如流系列 - 重建二叉树
面试题6:重建二叉树 题目描述: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8} ...
- LeetCode刷题总结-链表
LeetCode刷题总结-链表 一.链表 链表分为单向链表.单向循环链表和双向链表,一下以单向链表为例实现单向链表的节点实现和单链表的基本操作. 单向链表 单向链表也叫单链表,是链表中最简单的 ...
- sql 映射文件
...
- HanLP《自然语言处理入门》笔记--6.条件随机场与序列标注
笔记转载于GitHub项目:https://github.com/NLP-LOVE/Introduction-NLP 6. 条件随机场与序列标注 本章介绍一种新的序列标注模型条件随机场.这种模型与感知 ...