当前位置:网站首页>equals 和 hashCode

equals 和 hashCode

2022-06-21 09:28:00 凄戚

前言

假设你有一个容器使用哈希函数,当你创建一个对象放到这个容器时,你必须定义 hashCode 函数和 equals 方法。这两个函数一起被用于哈希容器中的查询操作。

一、equals 规范

当你创建一个类时,它自动继承自 Object 类,如果你不覆写 equals(),你将会获得object 对象的 equals() 函数。默认情况下,这个函数会比较对象的地址。所以只有你在比较同一个对象的时候,才会获得 true。

public class DefaultComparison {
    
    private int i, j, k;

    DefaultComparison(int i, int j, int k) {
    
        this.i = i;
        this.j = j;
        this.k = k;
    }

    public static void main(String[] args) {
    
        DefaultComparison
                a = new DefaultComparison(1, 2, 3),
                b = new DefaultComparison(1, 2, 3);
        System.out.println(a == a);
        System.out.println(a == b);
    }
}
/* output: true false */

这样的equals 明显不符合我们的期望,所以我们需要改写这个方法。
Java 7
下面的例子比较了不同类型的 Equality 类。为了避免重复的代码,我们使用了工厂函数设计模式来实现。

public interface EqualityFactory {
    
    Equality make(int i, String s, double d);
}

EqualityFactory 接口提供make()函数来生成一个 Equality 对象,这样不同的EqualityFactory 能生成 Equality 不同的子类。
现在定义 Equality ,它包含三个字段和一个 equals 函数用来满足上述条件。

public class Equality {
    
    protected int i;
    protected String s;
    protected double d;

    public Equality(int i, String s, double d) {
    
        this.i = i;
        this.s = s;
        this.d = d;
        System.out.println("made 'Equality'");
    }

    @Override
    public boolean equals(Object o) {
    
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!(o instanceof Equality)) return false;
        Equality equality = (Equality) o;
        if (!Objects.equals(i, equality.i)) {
    
            return false;
        }
        if (!Objects.equals(s, equality.s)) {
    
            return false;
        }
        if (!Objects.equals(d, equality.d)) {
    
            return false;
        }

// return i == equality.i && Double.compare(equality.d, d) == 0 && Objects.equals(s, equality.s);
        return true;
    }

    @Override
    public int hashCode() {
    
        return Objects.hash(i, s, d);
    }

    public void test(String descr, String expected, Object o) {
    
        System.out.format("----Testing %s --%n" + "%s instanceof Equality: %s%n" +
                        "Excepted %s, got %s%n",
                descr, descr, o instanceof Equality, expected, equals(o));
    }

    public static void testAll(EqualityFactory eqf) {
    
        Equality
                e = eqf.make(1, "Monty", 3.14),
                eq = eqf.make(1, "Monty", 3.14),
                neq = eqf.make(99, "Bob", 1.618);
        e.test("null", "false", null);
        e.test("same object", "true", e);
        e.test("different type", "false", Integer.valueOf(99));
        e.test("same values", "true", eq);
        e.test("different values", "false", neq);
    }

    public static void main(String[] args) {
    
        testAll(Equality::new);
    }
}
/* output: made 'Equality' made 'Equality' made 'Equality' ----Testing null -- null instanceof Equality: false Excepted false, got false ----Testing same object -- same object instanceof Equality: true Excepted true, got true ----Testing different type -- different type instanceof Equality: false Excepted false, got false ----Testing same values -- same values instanceof Equality: true Excepted true, got true ----Testing different values -- different values instanceof Equality: true Excepted false, got false */

testAll()执行了我们期望的所有不同类型对象的比较。它使用工厂创建了 Equality 对象。
但是上述的 equals() 函数繁琐,并且我们能够将其简化成规范的形式,请注意:

  1. instanceof 检查减少了 null 检查的必要。
  2. 和 this 的比较是多于的。一个正确书写的 equals()函数能正确地和自己比较。
class Part {
    
    String ss;
    double dd;

    Part(String ss, double dd) {
    
        this.ss = ss;
        this.dd = dd;
    }

    @Override
    public boolean equals(Object o) {
    
        /*if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Part part = (Part) o; return Double.compare(part.dd, dd) == 0 && Objects.equals(ss, part.ss);*/
        return o instanceof Part &&
                Objects.equals(ss, ((Part) o).ss) &&
                Objects.equals(dd, ((Part) o).dd);
    }

    @Override
    public int hashCode() {
    
        return Objects.hash(ss, dd);
    }
}
public class ComposedEquality extends SuccinctEquality{
    
    Part part;
    public ComposedEquality(int i, String s, double d) {
    
        super(i, s, d);
        part = new Part(s, d);
        System.out.println("made 'ComposedEquality'");
    }

    @Override
    public boolean equals(Object o) {
    
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        ComposedEquality that = (ComposedEquality) o;
        return Objects.equals(part, that.part);
    }

    @Override
    public int hashCode() {
    
        return Objects.hash(super.hashCode(), part);
    }

    public static void main(String[] args) {
    
        Equality.testAll(ComposedEquality::new);
    }
}
/* output: //这里有三对是因为初始化了三次 made 'Equality' made 'SuccinctEquality' made 'ComposedEquality' made 'Equality' made 'SuccinctEquality' made 'ComposedEquality' made 'Equality' made 'SuccinctEquality' made 'ComposedEquality' ----Testing null -- null instanceof Equality: false Excepted false, got false ----Testing same object -- same object instanceof Equality: true Excepted true, got true ----Testing different type -- different type instanceof Equality: false Excepted false, got false ----Testing same values -- same values instanceof Equality: true Excepted true, got true ----Testing different values -- different values instanceof Equality: true Excepted false, got false */

1.1 不同子类的相等性(如何定义 equals)

继承意味着两个不同子类的对象当其向上转型时可以是相等的。

enum Size{
     SMALL,MEDIUM,LARGE }

class Animal {
    
    private static int counter = 0;
    private final int id = counter++;
    private final String name;
    private final Size size;

    Animal(String name, Size size) {
    
        this.name = name;
        this.size = size;
    }

    @Override
    public boolean equals(Object o) {
    
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Animal animal = (Animal) o;
        return //id == animal.id && //[1]
                Objects.equals(name, animal.name) && size == animal.size;
    }

    @Override
    public int hashCode() {
    
        return Objects.hash(name, size);
// return Objects.hash(id, name, size);
    }

    @Override
    public String toString() {
    
        return String.format("%s[%d]: %s %s %x",
                getClass().getSimpleName(), id, name, size, hashCode());
    }
}
class Dog extends Animal {
    
    Dog(String name, Size size) {
    
        super(name, size);
    }
}

class Pig extends Animal {
    
    Pig(String name, Size size) {
    
        super(name, size);
    }
}

public class SubtypeEquality {
    
    public static void main(String[] args) {
    
        Set<Animal> pets = new HashSet<>();
        pets.add(new Dog("Ralph", Size.MEDIUM));
        pets.add(new Pig("Ralph", Size.MEDIUM));
        pets.forEach(System.out::println);
    }
}
/* output: Dog[0]: Ralph MEDIUM d1c09fb Pig[1]: Ralph MEDIUM d1c09fb */

我们在基类Animal 中定义 equals()hashCode(),并且这两个函数没有包含 id 字段。从 equals()的角度看,这意味着我们只关心它是否是 Animal,而不是它的子类
按道理这两个是一样的。它们有着相同的hashCode(),但是因为对象equals(),所以两个函数都出现在HashSet 中。

二、 哈希和哈希码

在集合中,我们使用预先定义的类作为 HashMap 的键。之所以可以这样,其实是因为预定义的类具有所必需的连线来使得它们的行为可以正确的作为keys
当创建自己的类作为 HashMap 的键时,很易犯的一个错误就是你经常忘了必要的连线。就是将这个类作为键时,没有重写它的 hashcode(),所以它继承的是 Object 的方法去根据对象的地址计算散列码,而对象 new 出的地址都不一样。

public class Groundhog {
    
    protected int number;

    public Groundhog(int number) {
    
        this.number = number;
    }

    @Override
    public String toString() {
    
        return "Groundhog #" + number;
    }
}

public class SpringDetector {
    
    public static <T extends Groundhog> void detectSpring(Class<T> type) {
    
        try {
    
            //获取到传入类的构造器
            Constructor<T> ghog = type.getConstructor(int.class);

            Map<Groundhog, Prediction> map =
                    IntStream.range(0, 10)//
                            .mapToObj(i -> {
    
                                try {
    
                                    return ghog.newInstance(i);
                                } catch (Exception e) {
    
                                    throw new RuntimeException(e);
                                }
                            })
                            toMap的两个参数都是根据传入的gh来运算的,两个参数用的是一样的值
                            .collect(Collectors.toMap(Function.identity(), gh -> new Prediction()));
            map.forEach((k, v) -> System.out.println(k + ": " + v));

            Groundhog gh = ghog.newInstance(3);
            System.out.println("Looking up prediction for " + gh);

            if (map.containsKey(gh)) {
    
                System.out.println(map.get(gh));
            }else System.out.println("Key not found: " + gh);
        } catch (NoSuchMethodException | IllegalAccessException |
                InstantiationException | InvocationTargetException e) {
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
        detectSpring(Groundhog.class);
    }
}
/* output: Groundhog #2: Six more weeks of Winter! Groundhog #9: Early Spring Groundhog #4: Six more weeks of Winter! Groundhog #7: Early Spring Groundhog #1: Early Spring Groundhog #5: Six more weeks of Winter! Groundhog #3: Early Spring Groundhog #6: Early Spring Groundhog #0: Six more weeks of Winter! Groundhog #8: Six more weeks of Winter! Looking up prediction for Groundhog #3 Key not found: Groundhog #3 */

每个Groundhog 都被赋予了一个常数, 这样我们就可以将它和匹配的 Predication 辨别出来。而 Predication 通过随机生成的 boolean 来选择天气。detectSpring()通过反射来实例化 Groundhog 类。
这里的 HashMap 被 Groundhog 和其相关联 的 Predication 填充。并且输出显示了 HashMap 中的内容,之后我们将填充了常数 3 的 Groundhog 作为key用于寻找对应的 Predication。(这个键值对肯定在map中)。
但是这并没有生效----它无法找到 数字 3 的这个键。问题出在 Groundhog 类自动地继承自基类 Object,所以这里用的是 Object 的hashCode() 生成散列码,而它(Object)默认是使用对象的地址计算散列码。因此,由 Groundhog(3) 生成的第一个实例的散列码(Map)与由 Groundhog(3) 生成的第二个实例的散列码(Groundhog)是不同的,而我们是拿后者进行查找的。
我们需要恰当的重写 hashCode() 方法。这意味着同时我们需要重写equals()方法,因为它也是Object的一部分。HashMap 需使用equals()判断当前的键是否与表中存在的键相同。

要记住:默认的 Object.equals() 只是比较对象的地址,而我们的两个 Groundhog(3) 的地址是不同的,因此,如果要使用自己的类作为 HashMap 的键,必须同时重载 hashCode()equals()

public class Groundhog2 extends Groundhog {
    
    public Groundhog2(int number) {
    
        super(number);
    }

    @Override
    //将自己的类作为键时,一定要在这里重写hashCode方法
    public int hashCode() {
    
        return number;
    }

    @Override
    public boolean equals(Object o) {
    
        return o instanceof Groundhog2 &&
                Objects.equals(number, ((Groundhog2) o).number);
    }
}
public class SpringDetector2 {
    
    public static void main(String[] args) {
    
        SpringDetector.detectSpring(Groundhog2.class);
    }
}
/* output: Groundhog #0: Six more weeks of Winter! Groundhog #1: Early Spring Groundhog #2: Six more weeks of Winter! Groundhog #3: Early Spring Groundhog #4: Early Spring Groundhog #5: Six more weeks of Winter! Groundhog #6: Early Spring Groundhog #7: Early Spring Groundhog #8: Six more weeks of Winter! Groundhog #9: Six more weeks of Winter! Looking up prediction for Groundhog #3 Early Spring */

Groundhog2.hashCode() 返回Groundhog 的标识数字作为散列码。hashCode()并不总是能返回唯一标识符(稍后阐明),但是equals()方法必须严格地判断两个对象是否相同。

2.1 理解 hashCode

前面的例子只是正确解决问题的第一步。它直说明:如果你不为你的键覆盖 hashCode()equals(),那么使用散列数据结构的对象(HashSet、HashMap、LinkedHashSet、LinkedHashMap)就无法正确处理你的键。所以你必须要很好的理解这些数据结构的内部构造。

public class MapEntry<K,V> implements Map.Entry<K,V>{
    
    private K key;
    private V value;

    public MapEntry(K key, V value) {
    
        this.key = key;
        this.value = value;
    }

    @Override
    public K getKey() {
    
        return key;
    }

    @Override
    public V getValue() {
    
        return value;
    }

    @Override
    public V setValue(V value) {
    
        V result = this.value;
        this.value = value;
        return result;
    }

    @Override
    public boolean equals(Object o) {
    
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MapEntry<?, ?> mapEntry = (MapEntry<?, ?>) o;
        return Objects.equals(key, mapEntry.key) && Objects.equals(value, mapEntry.value);
    }

    @Override
    public int hashCode() {
    
        return Objects.hash(key, value);
    }

    @Override
    public String toString() {
    
        return key + " = " + value;
    }
}

当你定义含有超过一个属性的对象的 hashCode() 时,你可以使用这个方法。如果你的对象只有一个属性时,可以直接使用 Objects.hashCode()。

public class SlowMap<K,V> extends AbstractMap<K,V> {
    
    private List<K> keys = new ArrayList<>();
    private List<V> values = new ArrayList<>();

    @Override
    public V get(Object key) {
    
        if (!keys.contains(key)) return null;
        return values.get(keys.indexOf(key));
    }

    @Override
    public V put(K key, V value) {
    
        V oldValue = get(key);//旧的值可能为null
        if (!keys.contains(key)) {
    
            keys.add(key);
            values.add(value);
        } else values.set(keys.indexOf(key), value);
        return oldValue;
    }

    @Override
    //这里的 Set 可以使得键值对唯一
    public Set<Map.Entry<K, V>> entrySet() {
    
        Set<Map.Entry<K, V>> set = new HashSet<>();
        Iterator<K> ki = keys.iterator();
        Iterator<V> vi = values.iterator();
        while (ki.hasNext()) {
    
            set.add(new MapEntry<>(ki.next(), vi.next()));
        }
        return set;
    }

    public static void main(String[] args) {
    
        SlowMap<String, String> map = new SlowMap<>();
        map.putAll(Countries.capitals(8));
        map.forEach((k,v)-> System.out.println(k+" = "+v));
        System.out.println(map.get("BENIN"));
        map.entrySet().forEach(System.out::println);
    }
}
/* output: CAMEROON = Yaounde ANGOLA = Luanda BURKINA FASO = Ouagadougou BURUNDI = Bujumbura ALGERIA = Algiers BENIN = Porto-Novo CAPE VERDE = Praia BOTSWANA = Gaberone Porto-Novo CAMEROON = Yaounde ANGOLA = Luanda BURKINA FASO = Ouagadougou BURUNDI = Bujumbura ALGERIA = Algiers BENIN = Porto-Novo CAPE VERDE = Praia BOTSWANA = Gaberone */

首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap 或者你自己实现的 Map 也可以达到此目的。
上面的示例用一对 ArrayList 实现了一个 Map,这个类包含了Map接口的完整实现,因此提供了 entrySet() 方法。
put 方法只是将键值对放入相应的 ArrayList。为了与 Map 接口保持一致,它必须返回旧的键,或者在没有任何旧键的情况下返回null。
注意:在get()中key 的类型是 Object,而不是你所期望的参数化类型K,这是将泛型注入到Java 语言中的结果。
Map.entrySet() 方法必须产生一个 Map.Entry 对象集。但是这个对象集只是一个接口,如果你想定义自己的map,就必须同时实现这个接口。

尽管上述解决方案十分简单,但是这并不是一个恰当的实现,因为它创建了键和值的副本。entrySet 的恰当实现应该在Map中提供视图,而不是副本,并且这个视图允许对原始映射表进行修改(而副本就不行)。

2.2 为了速度而散列(散列的原理)

SlowMap.java 说明了创建一个新的Map 是可行的,但是正如它的名字一样,它效率不高。它的问题在于对键的查询,键没有按照任何特定顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式
而散列的价值就在于速度,我们知道,散列查询的时间复杂度很低,效率是非常高的。
由于瓶颈位于的查询速度,因此解决方案之一就是保持键的排序状态,然后使用 Collections.binarySearch() 进行查询。
散列则更近一步,它将键保存在某处,以便很快可以找到。而将与键匹配的键的信息(key information)存储在数组(存取最快的数据结构)中,而这个所谓键的信息就是我们所熟知的散列码(记得数组并不保存键本身):由定义在 Object 中的、且可能由你的类覆盖的hashCode()方法(数据结构中的散列函数)生成。通过键对象生成一个数字,将其作为数组的下标。
于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。在使用散列码的时候,由于我们函数的不同,产生冲突的可能也不一样,一般产生冲突都由外部链接(俗称拉链法)解决:数组不直接保存值,而是保存值的list。然后对 list 中的值使用equals()进行线性的查询。这部分会慢一些,但是如果散列函数好的话,数组的每个位置并不会有太多的值。以上就是为什么 HashMap 这么快的总体原因,更详细的可以去看数据结构的散列这一部分。

public class SimpleHashMap<K,V> extends AbstractMap<K, V> {
    
    //选择一个初始数字作为哈希表的大小,以实现数据的均匀分布
    static final int SIZE = 997;
    //你不能有一个真正的的泛型数组,但你可以向上转换一个,这个数组用来实现拉链法
    LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];

    @Override
    public V get(Object key) {
    
        int index = Math.abs(key.hashCode()) % SIZE;
        if (buckets[index] == null) return null;
        for (MapEntry<K, V> entry : buckets[index])
            if (entry.getKey().equals(key))
                return entry.getValue();
        return null;
    }

    @Override
    public V put(K key, V value) {
    
        V oldValue = null;
        //将hashCode 的绝对值对SIZE取余
        int index = Math.abs(key.hashCode()) % SIZE;
        //给每个数组下标链接一个list
        if (buckets[index] == null) {
    
            buckets[index] = new LinkedList<>();
        }

        //获取到散列码下对应的list
        LinkedList<MapEntry<K, V>> bucket = buckets[index];
        MapEntry<K, V> pair = new MapEntry<>(key, value);
        boolean found = false;
        ListIterator<MapEntry<K, V>> it = bucket.listIterator();
        while (it.hasNext()) {
    
            MapEntry<K, V> entry = it.next();
            //根据匹配的Map的key值替换已经有的
            if (entry.getKey().equals(key)) {
    
                oldValue = entry.getValue();
                it.set(pair);
                found = true;
                break;
            }
        }
        //
        if (!found) {
    
            buckets[index].add(pair);
        }
        return oldValue;
    }

    @Override
    //每个map都可以生成一个entrySet
    public Set<Entry<K, V>> entrySet() {
    
        Set<Map.Entry<K, V>> set = new HashSet<>();
        for (LinkedList<MapEntry<K, V>> bucket : buckets) {
    
            if (bucket == null) continue;
            for (MapEntry<K, V> entry : bucket) {
    
                set.add(entry);
            }
        }
        return set;
    }

    public static void main(String[] args) {
    
        SimpleHashMap<String, String> m = new SimpleHashMap<>();
        m.putAll(Countries.capitals(8));
        m.forEach((k, v) -> System.out.println(k + "=" + v));
        System.out.println(m.get("BENIN"));
        m.entrySet().forEach(System.out::println);
    }
}

由于散列表中的“槽位”(slot)通常称为桶位(bucket),因此我们将表示实际散列表的数组命名为 bucket,为使散列分布均匀,桶的数量通常使用质数(现在基本使用 2 的整数次方更好)。
注意:为了能够自动处理冲突,使用了一个LinkedList 的数组,每一个新的元素只是直接添加到 list 尾的某个特定桶位中。即使Java 不允许你创建泛型数组,那你也可以创建指向这种数组的引用。

//你不能有一个真正的的泛型数组,但你可以向上转换一个,这个数组用来实现拉链法 LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];
这里,向上转型为这种数组是很方便的,这样可以防止在后面的代码中进行而额外的转换。

注意:这个实现并不意味着对性能进行了调优,它只是想要展示散列表执行的各种操作。而 java.util.HashMap 的源代码,则对其实现了调优。这里的entrySet() 也是过于简单的。

2.3 重写 hashCode()

在明白了如何散列之后,就可以编写自己的 hashCode() 了。
首先,你无法控制 bucket 数组的下标值的产生。这个值依赖于具体的 HashMap 对象的容量,而容量的改变与容器的充满程度和负载因子有关。hashCode() 生成的结果,经过处理后称为桶位的下标,上个示例中,只是对其取模。
设计hashCode() 最重要的因素就是:无论何时,对同一个对象调用 hashCode() 都会生成相同的值。
下面以String 类为例:String 有个特点:如果程序中有多个 String 对象,都包含相同的字符串序列,那么这些String 对象都会映射到同一块内存区域

public class StringHashCode {
    
    public static void main(String[] args) {
    
        String[] hellos = "Hello Hello".split(" ");
        System.out.println(hellos[0].hashCode());
        System.out.println(hellos[1].hashCode());
    }
}
/* output: 69609650 69609650 */

对于String 而言,hashCode() 的比较明显是基于 String 内容的。
因此,要想要 hashCode() 实用,它必须速度快且有意义。也就是说:它必须基于对象的内容生成散列码。散列码不必是独一无二的,但是通过 hashCode() 和 equals(),必须能够完全确定对象的身份。
哈希码的计算公式:result = 37 * result + c;
并且你编写hashCode() 的时候,要记得如下规则:使用 Objects.hash() 用于散列多字段的对象,然后使用Objects.hashCode()用于散列单字段的对象。
下面是单字段的对象:

public class CountedString {
    
    private static List<String> created = new ArrayList<>();
    private String s;
    private int id = 0;
    public CountedString(String str) {
    
        s = str;
        created.add(s);
        //id 是this类使用的String实例总数
        for (String s2 : created) {
    
            if (s2.equals(s))
                id++;
        }
    }

    @Override
    public int hashCode() {
    
        //很简单的实现:返回s.hashCode() * id;
        int result = 17;
        result = 37 * result + s.hashCode();
        result = 37 * result + id;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
    
        return obj instanceof CountedString &&
                Objects.equals(s,((CountedString) obj).s) &&
                Objects.equals(id,((CountedString) obj).id);
    }

    @Override
    public String toString() {
    
        return "String: " + s +" id: "+id+" hashCode: "+hashCode();
    }

    public static void main(String[] args) {
    
        Map<CountedString, Integer> map = new HashMap<>();
        CountedString[] cs = new CountedString[5];
        for (int i = 0; i < cs.length; i++) {
    
            cs[i] = new CountedString("hi");
            map.put(cs[i], i);
        }
        System.out.println(map);
        System.out.println("==================");
        for (CountedString cstring : cs) {
    
            System.out.println("Looking up " + cstring);
            System.out.println(map.get(cstring));
        }
    }
}
/* output: //记得打印 map 的时候,等于号‘=’前面所有的对应后面的 {String: hi id: 4 hashCode: 146450=3, String: hi id: 5 hashCode: 146451=4, String: hi id: 2 hashCode: 146448=1, String: hi id: 3 hashCode: 146449=2, String: hi id: 1 hashCode: 146447=0} ================== Looking up String: hi id: 1 hashCode: 146447 0 Looking up String: hi id: 2 hashCode: 146448 1 Looking up String: hi id: 3 hashCode: 146449 2 Looking up String: hi id: 4 hashCode: 146450 3 Looking up String: hi id: 5 hashCode: 146451 4 */

hashCode()equals() 都基于 CountString 的String 和 id 这两个字段来生成结果。在main中,使用相同的 String 创建了多个对象,但是因为id的不同,使得它们的散列码不同。

下面是第二个多字段示例:

public class Individual implements Comparable<Individual> {
    
    private static long counter = 0;
    private final long id = counter++;
    private String name;

    public Individual(String name) {
    
        this.name = name;
    }

    public Individual() {
    
    }

    @Override
    public String toString() {
    
        return getClass().getSimpleName() + (name == null ? "" : " " + name);
    }

    public long getId() {
    
        return id;
    }

    @Override
    public boolean equals(Object o) {
    
        return o instanceof Individual &&
            Objects.equals(id,((Individual) o).id);
    }

    @Override
    public int hashCode() {
    
        return Objects.hash(name,id);
    }

    @Override
    //compareTo 会产生一个排序序列,排序的规则首先按照实际类型排序,然后如果有名字的话,按照name排序,最后按照创建的顺序(id)排序
    public int compareTo(Individual o) {
    
        //首先根据类名比较
        String first = getClass().getSimpleName();
        String oFirst = o.getClass().getSimpleName();
        int firstCompare = first.compareTo(oFirst);
        if (firstCompare != 0) return firstCompare;
        if (name != null && o.name != null) {
    
            int secondCompare = name.compareTo(o.name);
            if (secondCompare != 0) {
    
                return secondCompare;
            }
        }
        return o.id < id ? -1:(o.id == id ? 0:1);
    }
}

三、调优HashMap

我们有可能手动调优 hashMap 以提高其在特定应用程序中的性能。
一下这些术语是必须了解的:

  • 容量(Capacity):表中存储的桶数量。
  • 初始容量(Initial Capacity):当表被创建时,桶的初始数量。HashMap 和 HashSet 有可以让你指定初始容量的构造器。
  • 个数(Size):目前存储在表中的键值对的个数。
  • 负载因子(Load factor):通常表现为 Size/Capacity,当负载因子为0 时表示一个空表。当大小为 0.5 表示半满,轻负载的表几乎没有冲突,因此是插入和删除的最佳选择,而0.75是同时考虑到时间和空间的平衡策略,HashMap 和 HashSet 有可以让你指定负载因子的构造器,当容量达到负载因子,集合就会自动扩充至原始容量的两倍,并将原始数据存储在新桶中。
原网站

版权声明
本文为[凄戚]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_43621315/article/details/118364294