Java中HashSet的重复性与判等运算重载

本文地址:http://www.javashuo.com/article/p-dnyssuae-kk.html
本文遵循CC BY-NC-SA 4.0协议,转载请注明出处
特别说明java

本文的基本语境是Java若是须要C#版本请看这里安全

还有一个故事……(平行世界篇)

这是一个关于另一个平行世界——Java中的类似的故事……ide

文艺复兴.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改为一个具名的类。this

而且还有一个善战的达拉崩巴:HashSet

更加巧合的是,在java.util里也有一个HashSet<E>,功能也是做为一个哈希集使用,也就是说它也知足以下两点:code

  1. 元素是惟一
  2. 元素是无序

What a COINCI-DANCE~~orm

并且,也是要分两种状况,值类型下,只要两个值相等,那么第二个元素就不会被添加:

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

可是,和上次同样的问题,尽管s4s1引用不一致,但实际场合下,咱们倾向于把它们看成同一我的,那么怎么办呢??

还有另一个故事(不是虚假传说)

不是虚假传说-序言

嗯,这个不是虚假的故事,这就是正经的解决方案,放心大胆的读吧!!

还有一对涂满毒药的夺命双匕:equals和hashCode

固然,Java里全部对象都继承自java.lang.ObjectObject,而Java对象也有两种相等判别方式:==Object.equals

并且,这俩判别方式如出一辙,值类型下只要值相等就能够,而对于引用类型,==判别的是引用一致性

可是为何此次标题里没有==的故事了??

一直就没有,那是你的错觉,上一篇的==仍是虚假的故事呢,并且缘由也很简单:

Java里运算符不容许重载

并且Object里没有以前的ReferenceEquals,因此==就是引用一致性的兜底判别,无法重载的话那就免谈了,不过equals是父类方法,固然是能够重载的。

那hashCode呢??

和隔壁的System.Object.GetHashCode()相似地,这边也有一个java.lang.Object.hashCode(),做用也是相似的,返回一个用做哈希值的数。

并且更加巧合的是,这里的Object.equals()hashCode()也没什么关系,单独改写其中一个函数对另一个函数也都没什么影响。

最最巧合的是,和隔壁同样,Java也建议equalshashCode要改都改
不过以前是由于非泛型容器(好比Hashtable),而此次是真真正正的为了泛型容器。

HashSet<E>正是使用equalshashCode做为双重判据,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
相关文章
相关标签/搜索