本文地址: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>,功能也是作为一个哈希集使用,也就是说它也满足如下两点:

  1. 元素是唯一的
  2. 元素是无序的

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的重复性与判等运算重载的更多相关文章

  1. C#中HashSet的重复性与判等运算重载

    目录 一个故事-- 一个繁荣的遥远国度:泛型容器 但是我也不确定容器里能放些什么东西啊 一个英勇的皇家骑士:HashSet 值类型的HashSet 引用类型的HashSet 另外一个--故--事?? ...

  2. Java中的二进制及基本的位运算

    Java中的二进制及基本的位运算 二进制是计算技术中广泛采用的一种数制.二进制数据是用0和1两个数码来表示的数.它的基数为2,进位规则是"逢二进一",借位规则是"借一当二 ...

  3. Java中HashSet的解读

    一. HashSet源代码 HashSet 的实现   对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的 ...

  4. Java中HashSet和HashMap

    Set中存储元素为什么不重复(即使hashCode相同)? HashSet中存放自定义类型元素时候,需要重写对象中的hashCode方法和equals方法, HashSet中存放自定义类型元素时候,需 ...

  5. Java中HashSet,HashMap和HashTable的区别

    HashMap.HashSet.HashTable之间的区别是Java程序员的一个常见面试题目,在此仅以此博客记录,并深入源代码进行分析: 在分析之前,先将其区别列于下面 1:HashSet底层采用的 ...

  6. java中HashSet详解(转)

    HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSe ...

  7. java中HashSet详解

    HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSe ...

  8. java集合(4)- java中HashSet详解

    HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSe ...

  9. java中hashSet原理

    转自: http://blog.csdn.net/guoweimelon/article/details/50804799 HashSet是JavaMap类型的集合类中最常使用的,本文基于Java1. ...

随机推荐

  1. 为什么 K8s 在阿里能成功?| 问底中国 IT 技术演进

    作者: 曾凡松 阿里云云原生应用平台高级技术专家 张振 阿里云云原生应用平台高级技术专家 导读:本文描述了阿里巴巴在容器管理领域的技术演进历程,解读了为什么 K8s 最终能够大获成功的原因,以及到今年 ...

  2. cocoa pods最新安装说明和使用方法

    最新版 CocoaPods 的安装流程 1.移除现有Ruby默认源 $gem sources --remove https://rubygems.org/ 2.使用新的源 $gem sources - ...

  3. Vue 组件复用性和slot

    1.组件可复用 2.slot元素作为组件模板之中的内容分发插槽,元素自身可以被替换 <!DOCTYPE html> <html lang="en"> < ...

  4. JS前端时间格式化

    var dateTime = new Date(tree_time); tree_time = dateTime.getFullYear() + '-'+ (dateTime.getMonth()+1 ...

  5. 解决android sdk无法更新 更新慢的问题

    使用不同平台开发android应用的时候都要先搭建开发环境. 这里介绍一下搭建开发环境过程中更新和下载android sdk的一种方法: 第一步:配置android sdk manager的代理服务, ...

  6. 玩转Django2.0---Django笔记建站基础六(模型与数据库)

    第六章 模型与数据库 Django对各种数据库提供了很好的支持,包括:PostgreSQL.MySQL.SQLite和Oracle,而且为这些数据库提供了统一的调用API,这些API统称为ORM框架. ...

  7. 提高Dom 访问效率

    在浏览器中对于Dom的操作和普通的脚本的操作处于两个不同的dll中,两个dll的交互是比较耗时的,优化对Dom的操作可以提高脚本的执行速度. JS访问DOM是很慢的,尽量不要用JS来设置页面 布局 有 ...

  8. NOI2.6 8782: 乘积最大

    描述 今年是国际数学联盟确定的"2000--世界数学年",又恰逢我国著名数学家华罗庚先生诞辰90周年.在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好 ...

  9. 关于不同python版本print不一致的简单解决方案

    经常遇到python2.x的print不带括弧,但python3.x必须要带括弧,版本不一致,需要修改,但是面对数以十计的重复劳动,不免望而却步.其他的一些不一样的地方同理. 解决方案: 运用正则化替 ...

  10. 拓扑排序(基于dfs+基于队列)

    经典问题-Ordering Tasks dfs函数的返回值表示是否成环,若存在有向环,则不存在拓扑排序.不包含有向环的有向图称为有向无环图(DAG) 可以借助DFS完成拓扑排序,在访问完一个结点时把他 ...