• 软件构造 | 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 
    
    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 中的指针直接指向内存地址,开发者可以直接控制和操作内存地址中的数据。

  • 相关阅读:
    UE 在场景或UMG中播放视频
    爬虫----js逆向某宝h5的sign参数
    OpenGL_Learn13(材质)
    Kernel Memory 中使用 PaddleSharp OCR
    5-3 批处理作业调度(回溯)
    深度掌握TypeScript中的重载【函数重载、方法重载】
    C语言自定义类型-结构体
    Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
    spring框架
    每 日 练 习
  • 原文地址:https://blog.csdn.net/m0_74314859/article/details/139704082