软件构造 | Equality in ADT and OOP

软件构造 | Equality in ADT and OOP

🧇1 Three ways to regard equality

1.1 Using AF to define the equality

ADT是对数据的抽象, 体现为一组对数据的操作

抽象函数AF:内部表示→抽象表示

基于抽象函数AF定义ADT的等价操作,如果AF映射到同样的结果,则等价

1.2 Using observation to define the equality

​ 站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这 两个对象是等价的。

在这里插入图片描述

1.3 == vs. equals()

== 引用等价性 : 对基本数据类型,使用==判定相等

equals() 对象等价性 : 对对象类型,使用equals()

在自定义ADT时,需要根据对“等价”的要求, 决定是否重写Object的equals()

1.4 Implementing equals()

The equals() method is defined by Object , and its default implementation looks like this:

在这里插入图片描述

Note:在Object中实现的默认equals()是在判断引用等价性,这通常不是程序员所期望的,因此,需要重写。

public classDuration {
    ...
    // Problematic definition of equals()
    public boolean equals(Duration that) {
		return this.getLength() == that.getLength();
    }
}

错误声明equal:实现的是重载而不是重写。

在这里插入图片描述

正确声明

在这里插入图片描述

在 Java 中,equals() 方法是用于比较两个对象是否相等的方法。在 Java 中,所有的类都继承自 Object 类,而 Object 类中的 equals() 方法默认实现是比较两个对象的引用是否相同(即比较内存地址)。因此,当需要在自定义类中比较对象内容时,通常需要重写 equals() 方法。

下面是一个示例,展示了如何重写 equals() 方法来比较自定义类中的对象内容:

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true; // 如果是同一个对象,直接返回true
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false; // 如果对象为null或者是不同类的实例,直接返回false
        }
        Person person = (Person) obj; // 强制转换为Person类
        return age == person.age && name.equals(person.name); // 比较name和age是否相等
    }

    // 省略 getter 和 setter 方法
}

public class Example {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Alice", 30);

        System.out.println(person1.equals(person2)); // 输出true,因为内容相同
    }
}

在上面的示例中,Person 类重写了 equals() 方法,根据对象的 nameage 属性来比较两个 Person 对象是否相等。在 equals() 方法中,首先比较对象引用是否相同,然后再比较类和属性是否相同。

需要注意的是,在重写 equals() 方法时,通常需要同时重写 hashCode() 方法,以确保两个方法的一致性。这样可以保证对象在放入基于散列的集合(如 HashMapHashSet)时能够正确地根据对象内容对其进行存储和检索。

🍕 2The Object contract(对象合同)

  1. 等价关系:自反、传递、对称。
  2. 除非对象被修改了,否则调用多次equals应是同样的结果。
  3. “相等”的对象,其hashCode()的结果必须一 致。

Note:用“是否为等价关系”检验你的equals()是否正确

2.1 Hash Tables

在这里插入图片描述

Object ’s default hashCode() implementation is consistent with its default equals()

在这里插入图片描述

键值对中的 key被映射为hashcode,对应到数组的index, hashcode决定了数据被存 储到数组的哪个位置

2.2 Overriding hashCode()

在 Java 中,每个对象都有一个 hashCode,它是对象的哈希码,用于确定对象在哈希表等数据结构中的存储位置。hashCode 的作用是为了更高效地进行对象的存储和检索,特别是在使用基于散列的集合(如 HashMapHashSet)时非常重要。

hashCode 方法的设计要求是:

  1. 如果两个对象通过 equals() 方法相等,则它们的 hashCode 必须相等。
  2. 如果两个对象的 hashCode 相等,它们并不一定通过 equals() 方法相等。

在实现类中重写 hashCode 方法时,通常需要保证满足上述两个要求。通常情况下,可以利用对象的属性来生成 hashCode 值,这样可以确保同样属性的对象具有相同的 hashCode

最简单方法:让所有对象的hashCode为同一 常量,符合contract,但降低了hashTable效率

通过 equals计算中用到的所有信息的hashCode组合出新的hashCode

在 Java 中,当两个对象通过 equals() 方法相等且具有相同的 hashCode 值时,如果将这两个对象放入基于散列的集合(如 HashMapHashSet)中,只会存储一个对象。这是因为散列集合在存储对象时会先根据 hashCode 值确定对象在内部数据结构中的存储位置,然后再通过 equals() 方法来判断具体位置是否已经存在相同的对象。

具体来说,当向散列集合中添加一个对象时,首先会计算该对象的 hashCode 值,然后根据 hashCode 值找到对象在内部存储结构中的位置。如果在该位置处已经有一个对象存在,并且这个对象与新添加的对象通过 equals() 方法比较相等(即返回 true),那么新添加的对象不会被存储,以保证集合中不会存在重复的对象。

下面是一个简单示例,演示了两个相等的对象具有相同 hashCode 值时,只存储一个对象的情况:

import java.util.HashMap;
import java.util.Map;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + name.hashCode();
        result = 31 * result + age;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return this.name.equals(other.name) && this.age == other.age;
    }
}

public class Example {
    public static void main(String[] args) {
        Map<Person, String> personMap = new HashMap<>();

        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Alice", 30);

        personMap.put(person1, "Value 1");
        personMap.put(person2, "Value 2");

        System.out.println(personMap.size()); // 输出 1,因为两个对象相等且具有相同的 hashCode
    }
}

在上面的示例中,Person 类重写了 equals()hashCode() 方法,确保在两个对象具有相同属性值时返回 true 并且具有相同的哈希码。当将这两个相等的对象放入 HashMap 中时,只会存储一个对象,因为它们具有相同的 hashCode 值。因此,最终输出的大小是 1

Always override hashCode() when you override equals()

除非你能保证你的ADT不会被放入到Hash类型的集合类中

🍿 3引用的概念

在 Java 中,引用是指向对象的指针或句柄。在 Java 中,所有对象都是通过引用来操作的,而不是直接访问对象本身。当您创建一个对象时,实际上是在堆内存中为该对象分配了空间,并返回一个引用,这个引用指向堆中的对象。Java 的引用是一种高级抽象概念,开发人员无法直接控制对象所在的内存位置,只能通过引用去访问和操作对象。

与此相对应,C 语言中的指针是直接指向内存地址的变量。在 C 中,通过指针可以直接访问或修改内存地址中的数据。指针在 C 语言中被广泛用于实现动态内存分配、访问数组元素、操作数据结构等。

下面是一个简单的示例来对比 Java 中的引用和 C 中的指针:

在 Java 中:

public class Example {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = str1;
        
        System.out.println(str1); // Hello
        System.out.println(str2); // Hello
        
        str2 = "World";
        
        System.out.println(str1); // Hello
        System.out.println(str2); // World
    }
}

在这个示例中,str1str2 都是对象的引用,它们最初都指向同一个字符串对象"Hello"。当我们修改 str2 的值时,它指向了一个新的字符串对象"World",但str1 仍然指向原来的字符串对象"Hello"。

在 C 中:

#include <stdio.h>

int main() {
    int var = 10;
    int* ptr = &var;
    
    printf("Original value: %d\n", var); // 10
    printf("Value through pointer: %d\n", *ptr); // 10
    
    *ptr = 20;
    
    printf("Updated value: %d\n", var); // 20
    printf("Value through pointer: %d\n", *ptr); // 20
    
    return 0;
}

在这个示例中,ptr 是一个指向 var 变量的指针。通过 *ptr 可以访问或修改 var 的值。在这段代码中,我们通过指针修改了 var 的值,而不是通过变量名 var 直接修改。

综上所述,Java 中的引用是指向对象的抽象概念,开发者无法直接操作对象的内存地址,只能通过引用访问对象;而 C 中的指针直接指向内存地址,开发者可以直接控制和操作内存地址中的数据。

  • 26
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值