美文网首页
java 的对象比较

java 的对象比较

作者: going_hlf | 来源:发表于2020-11-10 02:38 被阅读0次

本文探讨一下java对象之间比较的三种方式

概念

  1. obj1 == obj2:比较两个对象的内存地址是否相同,即是否同一次new出来的对象。
  2. obj1.equals(obj2):默认情况下(Object中的实现),跟==效果相同,但是子类可以覆写该方法。
  3. obj.hashCode():用于标识某个对象的hash值。主要用于hashMap,hashSet等容器的key值查找,其值并不一定唯一。

使用规则

  1. ==永远用来比较两个对象的内存地址,无法修改它的作用。
  2. equals可以被覆写,以用来表达业务概念的相等(往往指内容相同)。
  3. 根据设计契约,equals被重写后,需要同步重写hashCode
  4. 满足==的两个对象,一定满足equals,且拥有相同的hashCode(暂时没有想到反例)。
  5. 满足equals的两个对象一定满足hashCode相等,但是反之不要求一定成立,但是尽量保证成立的概率高一些。

==非常容易理解,下面重点介绍下equalshashCode

equals

equals可以被重写,说明equals不是严格数学意义上的相等,而是业务概念上的相等。
一个合适的 equals()方法必须满足以下五点条件(on java8):

  • 反身性:对于任何 x, x.equals(x) 应该返回 true。
  • 对称性:对于任何 x 和 y, x.equals(y) 应该返回 true当且仅当 y.equals(x) 返回 true 。
  • 传递性:对于任何x,y,还有z,如果 x.equals(y) 返回 true 并且 y.equals(z) 返回 true,那么 x.equals(z) 应该返回 true。
  • 一致性:对于任何 x和y,在对象没有被改变的情况下,多次调用 x.equals(y) 应该总是返回 true 或者false。
  • 对于任何非null的x,x.equals(null)应该返回false。

可以通过下面的方式对上述条件的满足性进行测试:
下面是满足这些条件的测试,并且判断对象是否和自己相等(我们这里称呼其为右值):

  • 如果右值是null,那么不相等。
  • 如果右值是this,那么两个对象相等。
  • 如果右值不是同一个类型或者子类,那么两个对象不相等。
  • 如果所有上面的检查通过了,那么你必须决定 右值 中的哪些字段是重要的,然后比较这些字段。

Java 7 引入了 Objects 类型来帮助这个流程,这样我们能够写出更好的 equals() 函数。

hashCode

Object默认的hashCode方法是使用对象的地址计算散列码。

如果不为你的容器的键值类型覆写hashCode() 和equals() ,那么使用散列的数据结构(HashSet,HashMap,LinkedHashst或LinkedHashMap)就无法正确处理你的键值类型。

关于如何生成比较好的hash值。on java8中给出了建议,在 Objects 类中有一个非常熟悉的方法可以帮助创建 hashCode() 方法:Objects.hash()。当你定义含有超过一个属性的对象的 hashCode() 时,你可以使用这个方法。如果你的对象只有一个属性,可以直接使用 Objects.hashCode()

如果要自己设计Map和更快的查找,就需要自行设计高效的散列函数算法(即hashCode()的算法实现):查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。
这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。

设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。

hashCode只是辅助hash容器的快速查找,并不具备唯一性,如果出现冲突,可以借助equals来解决,因为equals具有唯一性,这里的唯一性不是数学意义上的,而是业务概念上的。(从业务概念上将,使用==未必总是合适的,==可能太严格了。)

在Effective Java Programming Language Guide(Addison-Wesley 2001)这本书中,Joshua Bloch为怎样写出一份像样的hashCode()给出了基本的指导:

  1. 给int变量result赋予某个非零值常量,例如17。
  2. 为对象内每个有意义的字段(即每个可以做equals)操作的字段计算出一个int散列码c:
字段类型 计算公式
boolean c = (f ? 0 : 1)
byte , char , short , or int c = (int)f
long c = (int)(f ^ (f>>>32))
float c = Float.floatToIntBits(f);
double long l =Double.doubleToLongBits(f);
c = (int)(l ^ (l >>> 32))
Object , where equals() calls equals() for this field c = f.hashCode()
Array 应用以上规则到每一个元素中
  1. 合并计算得到的散列码: result = 37 * result + c;​
  2. 返回 result。
  3. 检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。

实验

我们通过两个实验来看下,各种比较的结果

  1. 基础示例
import java.util.Objects;

class DummyClass {
    public int dummyPara = 0;
};

class DummySwapper {
    DummyClass obj = new DummyClass();

    @Override
    public boolean equals(Object rhs) {
        if (!(rhs instanceof DummySwapper)) { // 首先判断是不是同类型
            return false;
        }

        return obj.dummyPara == ((DummySwapper)rhs).obj.dummyPara; // 再判断成员的值是否相等
    }

    @Override
    public int hashCode() {
//        return Objects.hashCode(obj);  // --> 这种写法跟equals是不配套的,无法配合使用。
        return Objects.hashCode(obj.dummyPara);
    }

    public static void main(String... args) {
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(str1.equals(str2)); // -> true
        System.out.println(str1 == str2); // -> false
        String str3 = new String(str1);
        System.out.println(str1.equals(str3)); // -> true
        System.out.println(str1 == str3); // -> false

        char value1[] = {'a', 'b', 'c'};
        char value2[] = {'a', 'b', 'd'};
        System.out.println(value1 == value2); // -> false
        System.out.println(value1.equals(value2)); // -> false
        value1 = value2;
        System.out.println(value1 == value2); // -> true
        System.out.println(value1.equals(value2)); // -> true

        DummyClass obj1 = new DummyClass();
        DummyClass obj2 = obj1;
        System.out.println(obj1.hashCode() == obj2.hashCode()); // -> true
        System.out.println(obj1.equals(obj2)); // -> true
        DummyClass obj3 = new DummyClass();
        System.out.println(obj1.hashCode() == obj3.hashCode()); // -> false
        System.out.println(obj1.equals(obj3)); // -> flase

        DummySwapper swapper1 = new DummySwapper();
        DummySwapper swapper2 = swapper1;
        System.out.println(swapper1.hashCode() == swapper2.hashCode()); // -> true
        System.out.println(swapper1.equals(swapper2)); // -> true
        DummySwapper swapper3 = new DummySwapper();
        System.out.println(swapper1.hashCode() == swapper3.hashCode()); // -> true
        System.out.println(swapper1.equals(swapper3)); // -> true
    }
}

输出

true
false
true
false
false
false
true
true
true
true
false
false
true
true
true
true
  1. hashSet的例子:
import java.util.HashSet;
import java.util.Objects;

enum LivingBeingType {
    ANIMAL,
    FRUIT,
}

class LivingBeing {
    public LivingBeingType type;

    LivingBeing(LivingBeingType type) {
        this.type = type;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(type);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        return obj instanceof LivingBeing && Objects.equals(type, ((LivingBeing)obj).type);
    }
}

class MyDog extends LivingBeing {
    MyDog() {
        super(LivingBeingType.ANIMAL);
    }
}

class MyCat extends LivingBeing {
    MyCat() {
        super(LivingBeingType.ANIMAL);
    }
}

class Apple extends LivingBeing {
    Apple() {
        super(LivingBeingType.FRUIT);
    }
}

class Orange extends LivingBeing {
    Orange() {
        super(LivingBeingType.FRUIT);
    }
}

class LivingBeingTest {
    public static void main(String... args) {
        HashSet<LivingBeing> objs = new HashSet<>();
        objs.add(new MyDog());
        objs.add(new MyCat());
        objs.add(new Apple());
        objs.add(new Orange());
        objs.forEach(e -> System.out.println(e));
    }
}

输出

Apple@34a245ab
MyDog@4e50df2e

可以看出,如果我们按照动物和水果来分类,则猫和狗被认为是同一个对象,苹果和橘子认为是同一个对象。所以最后hashSet中只有两个元素。

String类的例子

我们非常熟悉的String类,其实是重写了Object的equals方法和hashCode
方法。Object的equals方法就是使用==判断两个对象是否相等,hashCode方法返回该对象的内存地址值计算的一个hash值。
覆写后的equals方法,除了认为内存相同是相同对象外,还认为字符串完全相同,也是相同的对象。hashCode方法按照同样的概念做了覆写。

   // from jdk14.0.2
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (!COMPACT_STRINGS || this.coder == aString.coder) {
                return StringLatin1.equals(value, aString.value);
            }
        }
        return false;
    }

    public int hashCode() {
        // The hash or hashIsZero fields are subject to a benign data race,
        // making it crucial to ensure that any observable result of the
        // calculation in this method stays correct under any possible read of
        // these fields. Necessary restrictions to allow this to be correct
        // without explicit memory fences or similar concurrency primitives is
        // that we can ever only write to one of these two fields for a given
        // String instance, and that the computation is idempotent and derived
        // from immutable state
        int h = hash;
        if (h == 0 && !hashIsZero) {
            h = isLatin1() ? StringLatin1.hashCode(value)
                           : StringUTF16.hashCode(value);
            if (h == 0) {
                hashIsZero = true;
            } else {
                hash = h;
            }
        }
        return h;
    }

代码链接:https://gitee.com/haoliangfei/java_beginner/tree/master/objects_compare

参考:

  1. on java8
  2. stackOverflow

相关文章

  • java—对象的比较

    如果要判断2个对象是否相等,就要对该对象的属性内容进行比较,如果属性内容相同,则说明是同一个对象,标准方法如下: 记住:

  • java 的对象比较

    本文探讨一下java对象之间比较的三种方式 概念 obj1 == obj2:比较两个对象的内存地址是否相同,即是否...

  • Java 比较器 和 包装类

    Java比较器 背景: 在Java中经常会涉及到多个对象的排序问题,那么就涉及到对象之间的比较 Java中的对象,...

  • java对象比较排序

    问题:点击某个闭合多边形内部,比较点击点距离各条边的距离,取出距离最短的那条线的两个坐标点?思路:根据方法得到点到...

  • Java对象比较器

  • Java中对象的比较

    Java中要比较对象的大小或者要对对象的集合进行排序,需要通过比较这些对象的某些属性大小来确定它们之间的大小关系。...

  • Java中的对象比较

    一、对象的比较 在之前如果是两个数值的比较“==”,如果是字符串的比较一般用“equals()" 方法,这个方法是...

  • Java比较器(对象排序)

    Java实现排序的方式 自然排序:java.lang.Comparable 定制排序:java.lang.Comp...

  • Kotlin与Java比较:对象

    前言 Kotlin作为JVM系的语言,起源于Java又不同于Java。通过在语言层面比较两者的区别,可以使得开发者...

  • Java-集合对象比较

网友评论

      本文标题:java 的对象比较

      本文链接:https://www.haomeiwen.com/subject/toldbktx.html