首页 > 编程语言 >java OOP 对象操作

java OOP 对象操作

时间:2024-11-03 14:45:14浏览次数:5  
标签:java 对象 clone equals other OOP new public

 目录

对象比较

”引用比较“与“内容比较”

对象的比较:Comparable接口

泛型化的Comparable接口

使用例子

“==”与“equals”

重写equals()的必要性

重写equals方法的要求

重写hashCode( )方法

hashCode() 与 equals() 的关系

重写 hashCode() 的规则


前面的OOP部分都在讲类,这篇整理了一下对 对象本身的操作。
虽然自己还没对象,想new一个(bushi)

对象比较

”引用比较“与“内容比较”

▪ 对于基本数据类型的变量,可以使用“==”,“>”和“<”进行判等和大小比较,但除了“==”之外,“>”和“<”是不能直接用于引用类型的变量的。
▪ 经过前面的学习,我们己经知道,施加于两个对象变量之上的“==”,实际上是判断这两个对象变量是否引用同一个对象。
▪ 在实际开发中,我们经常需要比对某两个对象的“内容” ,比如你己经有了一个对象,想在另一个对象集合中找到是否有“内容一样”的对象。
▪ 这里所说的对象的“内容”,主要指对象的字段值。
▪ 由于字段值在不同的时刻可能会改变,在某一特定时刻,对象所有字段值的集合,称为对象在这一时刻的“状态”。

对象的比较:Comparable接口

JDK中为了能比较两个对象的大小,定义了以下接口:

public interface Comparable {
        int compareTo(Object other);
}

compareTo()方法的返回值,约定如下:

1. 对象X和Y相等,返回“0”
2. 对象X<Y,返回“-1”
3. 对象X>Y,返回“1”

泛型化的Comparable接口

引入泛型特性后,JDK中又增加了一个泛型化的接口,老的版本现在已经不再推荐使用了:

public inteface Comparable<T> {
        int compareTo(T other);
}

使用例子

import java.util.*;

public class StudentSortTest {
    public static void main(String[] args) {
        Student[] staff = new Student[3];

        staff[0] = new Student("张三", 25);
        staff[1] = new Student("李四", 20);
        staff[2] = new Student("王五", 10);
        //排序
        Arrays.sort(staff);

        for (Student e : staff)
            System.out.println("姓名=" + e.getName() + "; 年龄=" + e.getage());
    }
}

class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String n, int s) {
        name = n;
        age = s;
    }

    public String getName() {
        return name;
    }
    public double getage() {
        return age;
    }

    public int compareTo(Student other) {
        if (age < other.age) return -1;
        if (age > other.age) return 1;
        return 0;
    }

}

这个例子中手动实现了compareTo()方法。

Arrays.sort()方法会调用每个Student对象的compareTo()方法,以确定元素在数组中的顺序。

通过compareTo()方法,Array.sort() 会对数组中的两个元素两两比较,从而对staff数组进行排序。

这和c++中的sort自己写一个cmp函数是一个道理。

▪ JDK中凡是支持大小比较的类型(比如Integer),都实现了Comparable<T> 接口。

Integer 的源码:

“==”与“equals”

之前的文章中提到过,“==”施加于引用类型变量,是比较两个对象变量是否引用同一对象。如果需要比对对象的“内容(即各字段的值)”,通常是调用对象的equals方法。


▪ equals方法由Object类所定义,其默认实现如下:

public boolean equals(Object obj) {
        //默认情况下是比较对象引用,而不是对象“内容”
        return (this == obj);
}

子类可根据实际情况,“重写(Override)”Object类的equals方法。

重写equals方法,其实就是要你确定一下“两个对象怎样才算相等”。根据自己的具体需要来写


class MyClass {

    public int InnerValue;
    public String InnerString;

    public boolean equals(Object obj) {
        boolean result = obj instanceof MyClass;
        if (!result) {
            return false;
        } else {
            MyClass other = (MyClass) obj;
            return (other.InnerValue == this.InnerValue)
                    && (other.InnerString.equals(this.InnerString));
        }

    }
}

public class IsTwoObjectEquals {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.InnerString = "Hello";
        obj.InnerValue = 100;

        MyClass other = new MyClass();
        other.InnerString = "Hello";
        other.InnerValue = 100;

        System.out.println(obj.equals(other));
    }

}

就比如说这个代码中,就重写为:InnerValue相等时,两个对象相等。

重写equals()的必要性

▪ JDK的许多集合类型,比如ArrayList,在查找元素时,会调用元素的equals()方法以确定当前元素是不是要找的那个。

如果你写了一个类,把对象放到ArraryList集合中,那么,你需要重写equals方法,才能在集合中使用indexOf()方法查找到特定的对象,这个就是为什么你在定义类时,需要重写equals()方法的原因。

重写equals方法的要求

自反性(reflexive): x.equals(x)= true
对称性(symmetric):如果 x.equals(y) =true,那么 y.equals(x)=true
传递性(transitive):如果 x.equals(y) = true 并且y.equals(z) = true,那么x.equals(z)= true. (回想起离散数学的知识了)
一致性(consistent):只要对象的状态没有发生改变, x.equals(y)的多次调用应该返回一致的结果
x.equals(null) = false
equals()方法要与compareTo()方法的返回值一致。当equals()方法返回true时,compareTo()方法应该返回0。所以,为了保持两个方法结果的一致性,可以在重写equals()方法时,直接调用compareTo()方法,以避免相同的对象比较规则重复写两遍。

▪ 要重写Object的equals方法,注意其参数类型必须是“Object”,如果是其它类型,就是“重载(overload)”,导致该方法不会覆盖Object类的equals()方法。

public boolean equals(Object obj) {    //方法签名必须这样写才是重写
        //...
}

重写hashCode( )方法

▪ 另外,为了让对象能放入JDK所提供的各种集合中,通常还需要重写hashCode()方法,因为这些集合在内部可能会需要依据对象的hashCode值进行定位。

  • 在基于哈希的集合(如HashSetHashMap等)中,查找和存储对象时首先根据对象的hashCode值定位到存储桶(bucket)或区域,然后再使用equals()进行精确匹配。如果没有正确重写hashCode()方法,那么即使两个对象根据equals()相等,它们的hashCode值可能也不相同,导致集合无法找到目标对象。
hashCode()equals() 的关系
  • 相等性约定:如果equals()判断两个对象相等,那么它们的hashCode()值必须相同。
  • 反之并不要求:如果两个对象的hashCode()值相同,equals()不一定返回true,这是因为hashCode()值相同只是意味着它们可能在同一个存储区域,具体相等性还需equals()来判断。
重写 hashCode() 的规则

重写hashCode()时可以基于对象的属性生成哈希值,确保当equals()判断两个对象相等时,它们的hashCode()也相等。例如:

@Override
public int hashCode() {
    return Objects.hash(age); // 使用属性生成哈希值
}

Objects.hash( )是Java提供的一个方法,它可以根据传入的属性自动生成哈希值,并确保符合hashCodeequals的一致性要求。

  • 必须同时重写equals()hashCode(),确保对象在集合中基于逻辑相等性进行查找和存储。
  • equals()定义逻辑相等性hashCode()提供有效的散列支持。
  • 若只重写equals()hashCode(),会导致在集合中存取对象时出现异常行为或无法查找的情况。

对象组合

对象组合指的是对象之间的相互包容关系。
在面向对象的语言中:

对象的两种组合方式

▪ 一个对象包容另一个对象,称为“对象组合”。

合成(Composition)

合成是更强的“整体-部分”关系,意味着部分对象的生命周期依赖于整体对象。

▪ A完全包容B。
▪ A对象创建时,B对象自动创建,同样地,A对象销毁时,B对象也同时销毁。

例如,一个House类由多个Room组成,但Room不能脱离House单独存在。如果销毁House,则Room也会随之销毁。

  • 特性:合成关系中“部分”对象依赖于“整体”对象的生命周期。
  • 实现:在Java中,通过在“整体”对象构造和销毁时创建或销毁“部分”对象来实现。
class House {
    private Room room; // 合成关系

    public House() {
        room = new Room(); // 创建House时创建Room
    }

    public Room getRoom() {
        return room;
    }
}

class Room {
    // Room的生命周期依赖House
}

在此例中,Room的生命周期与House一致,当House销毁时,Room也随之销毁。

关联(Association)

关联指两个对象之间有某种连接或依赖关系,但彼此可以独立存在。例如,学生和课程之间的关系:学生参加某些课程,课程也可以包含多个学生,但二者在生命周期上是独立的。

  • 特性:对象在关系中互相引用但仍可以独立存在。
  • 实现:在Java中,可以通过字段引用来实现关联。比如,Student类中包含Course的引用,表示学生与课程的关联关系
class Student {
    private String name;
    private Course course; // 学生关联到某个课程

    public Student(String name, Course course) {
        this.name = name;
        this.course = course;
    }

    public String getCourseName() {
        return course.getCourseName();
    }
}

class Course {
    private String courseName;

    public Course(String courseName) {
        this.courseName = courseName;
    }

    public String getCourseName() {
        return courseName;
    }
}

在此例中,Student类和Course类通过course字段相互关联,但二者独立存在,可以相互替换或修改,而不会影响对方。

实际使用

比如,在桌面应用中,窗体与窗体上各种控件之间的关系,就是第一种对象组合方式,窗体对象负责管理控件对象的生命周期,当窗体对象销毁时,所有控件对象也随之销毁。

JDK中的许多集合,比如ArrayList<T>,它与放在集合中的对象之间,就是第二种对象组合方式,集合对象自己,与放在集合中的对象,其生命周期是相互独立的。

对象复制

▪ 所谓“对象复制”,是指这样的一种情景:我己经有一个对象A了,我希望把它克隆N份,得到N个与A内容一模一样的对象。

浅拷贝

先举一个简单的例子:

package shallow;

class A {
    public int i = 100;
}

public class ShallowCopy {

    public static void main(String[] args) {
        A a = new A();
        //开始克隆
        A other = CloneObject(a);
        System.out.println(a == other);

    }

    static A CloneObject(A obj) {
        A newObj = new A();
        newObj.i = obj.i;
        return newObj;
    }
}

前面的示例代码中,对象的复制其实如下图所示:

像这样,把一个对象的所有字段值,逐个地复制到另一个对象的对应字段,这种对象复制方式称为“浅拷贝(shallow copy)”。

对于组合对象,浅拷贝方式会带来一些问题。(研究过py创建二维列表的应该会有印象)

以下示例代码,注意到A对象包容一个B对象:

package shallow;

class A2 {

    public int i = 100;
    public B2 b;        //A包容一个B的对象

    public A2() {
        b = new B2();	//创建被包容对象
    }
}

class B2 {
    public int j = 200;
}

public class ShallowCopy2 {

    public static void main(String[] args) {
        A2 a = new A2();
        A2 other = CloneObject(a); //克隆对象
        System.out.println(a==other);
        System.out.println(a.b==other.b);
    }

    static A2 CloneObject(A2 obj) {
        A2 newObj = new A2();
        newObj.i = obj.i;
        newObj.b = obj.b; //用于完成空对象工作的方法
        return newObj;
    }
}

在这个例子中,对象B没有被复制,而是被克隆前后的两个对象所“共享”,这并不符合“对象复制”的本意。我们希望完成的是“对象克隆”,即得到两个“一模一样的”,并且是“完全独立”的对象。

所以为了真正实现复制对象的功能,就需要进行深拷贝。

深拷贝

手动拷贝

我们可以手动实现深拷贝需要个功能,就是把字段一个个复制过去。

package deep;

class A {

    public int i = 100;
    public B b;        //A包容一个B的对象

    public A() {
        b = new B();    //创建被包容对象
    }
}

class B {
    public int j = 200;
}

public class DeepCopy {

    public static void main(String[] args) {
        A a = new A();
        A other = cloneObject(a);
        System.out.println(a == other);
        System.out.println(a.b == other.b);

    }

    static A cloneObject(A obj) {
        A newObj = new A();
        newObj.i = obj.i;
        //创建一个被包容的内部对象
        newObj.b = new B();
        newObj.b.j = obj.b.j;
        return newObj;
    }
}

Cloneable接口:递归调用clone()方法

JDK中提供了一个Cloneable接口,需要实现深复制的对象应该实现这一接口。

Cloneable接口本身并没有定义任何方法,它的作用只是标记对象是可克隆的。真正执行克隆的是Object类的clone()方法,但我们通常需要覆盖该方法以实现深拷贝。(像这种根本就没有定义任何一个成员的接口,称为“标记接口”。)

Object类提供了一个protected clone()方法,子类可将其定义为public的,从而向外界提供克隆自己的功能:依据JDK文档,一个对象选择实现Cloneable接口,必须重写Object类的clone方法,并把它改写为public的。

class DeepCopyDemoClass implements Cloneable {

    public int i = 100;
    public InnerClass b;        //A包容一个B的对象

    public DeepCopyDemoClass() {
        b = new InnerClass();	//创建被包容对象
    }
    //重写基类的clone方法
    public Object clone(){
        var newObj = new DeepCopyDemoClass();
        newObj.i = this.i;
        newObj.b = new InnerClass();
        newObj.b.j=this.b.j;
        return newObj;
    }
}

class InnerClass {
    public int j = 200;
}

public class DeepCopy2 {
    public static void main(String[] args) {
        var a = new DeepCopyDemoClass();
        var other = (DeepCopyDemoClass)a.clone();
        System.out.println(a == other);
        System.out.println(a.b == other.b);
    }
}

当我们希望实现一个对象的深拷贝时,递归地调用clone()方法就是指,如果对象包含了其他引用类型的对象,这些对象本身也需要实现Cloneable接口,并覆盖它们的clone()方法。这样每个引用类型对象都可以生成自己独立的副本。

基本步骤

  • 实现Cloneable接口:在需要克隆的类上实现Cloneable接口,表明该类是可克隆的。
  • 覆盖clone()方法:覆盖类的clone()方法,调用super.clone()来创建当前对象的浅拷贝。
  • 递归克隆引用字段:对于每个引用字段,调用它们的clone()方法,这样就实现了深拷贝。
  • 异常处理clone()方法要求处理CloneNotSupportedException异常。
class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 调用Object的浅拷贝
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 1. 首先浅拷贝Person对象自身
        Person clonedPerson = (Person) super.clone();

        // 2. 调用address字段的clone()方法,实现深拷贝
        clonedPerson.address = (Address) address.clone();

        return clonedPerson;
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person p1 = new Person("Alice", address);

        // 深拷贝Person对象
        Person p2 = (Person) p1.clone();

        System.out.println(p1.address.city); // 输出: New York
        System.out.println(p2.address.city); // 输出: New York

        // 修改p1的address对象不会影响p2的address对象
        p1.address.city = "Los Angeles";
        System.out.println(p1.address.city); // 输出: Los Angeles
        System.out.println(p2.address.city); // 输出: New York
    }
}

代码说明

  1. Address

    • Address实现了Cloneable接口,并重写了clone()方法。
    • clone()中调用super.clone()来创建当前对象的浅拷贝(因为Address没有复杂的引用类型属性,这样的浅拷贝已足够)。
  2. Person

    • Person也实现了Cloneable接口并重写了clone()方法。
    • Personclone()方法中,首先调用super.clone()创建对象的浅拷贝。
    • 然后,对包含的引用类型address字段也调用其clone()方法,从而实现对address对象的深拷贝。
  3. 测试效果

    • 调用p1.clone()生成p2后,p1p2对象中的address字段不再指向同一个引用,确保修改p1.address的内容不会影响p2.address

如果一个类中包含多个嵌套的引用对象,例如Person包含Address对象,Address还包含City对象,则我们需要递归地在每一个嵌套的引用对象上实现clone()方法。每一个引用对象都要实现Cloneable接口并重写clone()方法,这样递归的调用会实现整个对象树的深拷贝。

标签:java,对象,clone,equals,other,OOP,new,public
From: https://blog.csdn.net/sjb2274540432/article/details/143304199

相关文章

  • 数据库大型对象类型
    一、引言在现代数据库应用中,除了存储简单的数值、文本等数据类型外,常常需要处理一些大型对象数据,如图片、音频、视频、大篇幅的文档等。为了满足这些需求,数据库引入了特定的大型对象类型,包括BLOB(BinaryLargeObject)、CLOB(CharacterLargeObject)、RAW、LONGRAW等。这些类......
  • Java类和对象(上篇)
    今天学习Java的类【认识类,并学习有关类的操作(1.定义和使用2.实例化3.this引用4.构造对象和初始化对象)】目录1.初步认知面向对象1.1面向对象的概念1.1面向对象和面向过程2.类定义和使用2.1认识类2.2类的定义格式2.3练习2.3.1定义一个狗类2.3.2定义一个学生......
  • 华为OD机试-E卷,100分 - 最小的调整次数特异性双端队列Java & Python& JS & C++ & C
    最新华为OD机试题目描述有一个特异性的双端队列,该队列可以从头部或尾部添加数据,但是只能从头部移出数据。小A依次执行2n个指令往队列中添加数据和移出数据。其中n个指令是添加数据(可能从头部添加、也可能从尾部添加),依次添加1到n;n个指令是移出数据。现在要求移除数据的顺......
  • 华为OD机试-E卷100分 -货币单位换算Java & Python& JS & C++ & C
    最新华为OD机试题目描述记账本上记录了若干条多国货币金额,需要转换成人民币分(fen),汇总后输出。每行记录一条金额,金额带有货币单位,格式为数字+单位,可能是单独元,或者单独分,或者元与分的组合。要求将这些货币全部换算成人民币分(fen)后进行汇总,汇总结果仅保留整数,小数部分舍弃......
  • 基于 JAVASSM 框架沙县小吃点餐系统
    基于JAVASSM框架(即Java+Spring+SpringMVC+MyBatis)开发一个沙县小吃点餐系统。步骤一:需求分析明确系统需要实现的功能,比如:用户注册和登录浏览菜单添加菜品到购物车下单并支付订单管理后台管理(菜品管理、订单管理等)步骤二:设计数据库使用MySQL数据库存储系统......
  • 【java开发】FileWriter
    原创大常运维FileWriter(文件字符输出流):作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。构造函数和方法:代码:packagecn.chang.d1_char_stream;importjava.io.File;importjava.io.FileWriter;importjava.io.IOException;importjava.io.Writer;......
  • 面向对象试题带答案
    一、选择题 (1)下列函数中(1)是不能重载的。A)成员函数B)非成员函数C)析构函数D)构造函数(2)下列重载函数的描述中,(2)是错误的。A)重载函数中不允许使用默认参数B)重载函数中编译系统根据参数表进行选择C)不要使用重载函数来描述毫不相干的函数D)构造函数重载将......
  • Hadoop分布式文件系统架构和设计
    Hadoop分布式文件系统架构和设计引言Hadoop分布式文件系统(HDFS)是一个设计用于在普通硬件上运行的分布式文件系统。它与现有的分布式文件系统有许多相似之处。然而,HDFS与其他分布式文件系统的差异是显著的。HDFS具有高度的容错能力,并且设计用于在低成本硬件上部署。HD......
  • 一文彻底熟练掌握并使用Java的NIO操作
    一、基本概念JavaNIO是Java1.4引入的,用于处理高速、高并发的I/O操作。与传统的阻塞I/O不同,NIO支持非阻塞I/O和选择器,可以更高效地管理多个通道。二、核心组件通道(Channel)Channel是NIO中用于读取和写入数据的主要接口,提供双向数据传输的能力。常见的通道......