java OOP 对象操作

 目录

对象比较

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

对象的比较: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;}@Overrideprotected 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;}@Overrideprotected 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 YorkSystem.out.println(p2.address.city); // 输出: New York// 修改p1的address对象不会影响p2的address对象p1.address.city = "Los Angeles";System.out.println(p1.address.city); // 输出: Los AngelesSystem.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()方法,这样递归的调用会实现整个对象树的深拷贝。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/459197.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小柴冲刺软考中级嵌入式系统设计师系列二、嵌入式系统硬件基础知识(6)嵌入式系统总线及通信接口

目录 越努力&#xff0c;越幸运&#xff01; flechazo 小柴冲刺软考中级嵌入式系统设计师系列总目录 一、PCI、PCI-E 等接口基本原理与结构 1、PCI (1)高速性。 (2)即插即用性。 (3)可靠性。 (4)复杂性。 (5)自动配置。 (6)共享中断。 (7)扩展性好。 (8)多路复用。…

DDei在线设计器-简洁布局

简洁布局 从1.2.41版开始&#xff0c;DDei提供了简洁版界面布局&#xff0c;效果如下&#xff1a;。 一、使用方式 demo.vue <script setup lang"ts"> import DDeiEditorView from "ddei-editor"; import { DDeiCoreStandLayout } from "ddei…

我的随机数生成有什么问题,它只给出一个数字作为输出

问题 我尝试过更改数字&#xff0c;还尝试过删除并重新添加数学层&#xff0c;但结果仍然相同。它每次都应该给出不同的数字&#xff0c;但在我的例子中&#xff0c;无论我编写代码多少次&#xff0c;它都只给出一个数字作为输出。 我的代码是 var thisIsNew Math.floor(Mat…

数据库存储过程的后端调用(SQLServer)

项目中使用到了SQL Server的存储过程&#xff0c;其位于可编程性-存储过程&#xff0c;详细如下。 其内需要四个入参&#xff0c;为表名&#xff0c;列名&#xff0c;左固定值&#xff0c;剩下右边的长度。 本项目中需要生成10位验收单编号&#xff0c;其中前六位为年月&#…

部署MiniCPM-V

GitHub - OpenBMB/MiniCPM-V: MiniCPM-V 2.6: A GPT-4V Level MLLM for Single Image, Multi Image and Video on Your Phone 安装和执行 "Local WebUI Demo" 的步骤如下&#xff1a; 克隆仓库并导航到源文件夹&#xff1a; git clone https://github.com/OpenBMB/M…

第十二章 章节练习created的应用

目录 一、引言 二、运行效果图 ​三、完整代码 一、引言 构建一个新闻的页面&#xff0c;页面在响应式数据准备好之后&#xff08;即created&#xff09;&#xff0c;就向后台接口请求获取新闻数据列表&#xff0c;然后赋值给Vue实例中的list列表&#xff0c;这个请求逻辑我…

智能管线巡检系统:强化巡检质量,确保安全高效运维

线路巡检质量的监控是确保线路安全、稳定运行的重要环节。为了有效监控巡检质量&#xff0c;采用管线巡检系统是一种高效、科学的手段。以下是对如何通过管线巡检系统实现线路巡检质量监控的详细分析&#xff1a; 一、巡检速度监控 管线巡检系统能够实时监控巡检人员的巡检速度…

【52 机器学习 | 基于KNN近邻和随机森林模型对用户转化进行分析与预测】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 字段说明2.4 删除重复值2.5 删除空值 &#x1f3f3;️‍&#x1f308; 3. 数据分析-特征分析3.1 年龄及转化率分析3.2 各营销渠道人数及…

2024年下教师资格证面试报名详细流程❗

⏰ 重要时间节点&#xff1a; &#xff08;一&#xff09;下半年笔试成绩查询&#xff1a;11月8日10:00 &#xff08;二&#xff09;注册报名&#xff1a;11月8日10:00-11日18:00 &#xff08;三&#xff09;网上审核&#xff1a;11月8日10:00-11日18:00 &#xff08;四&#x…

vue2之混入(mixin)

Vue 2 的混入&#xff08;Mixin&#xff09;是一种在 Vue 组件中分发可复用功能的方式。通过混入&#xff0c;你可以将一些通用的组件选项&#xff08;如数据、方法、计算属性、生命周期钩子等&#xff09;提取到一个混入对象中&#xff0c;并在多个组件中重用这些选项&#xf…

粉体包覆机、粉体干燥机、球磨机、整形机

包覆改性机在粉体加工中的应用和重要性主要体现在以下几个方面&#xff1a; 1.行业范围广泛&#xff1a;制药&#xff1a;用于药物载体、药物添加剂的生产&#xff0c;通过表面改性改善药物的释放性能、稳定性和生物相容性等&#xff1b;化妆品&#xff1a;用于口红、粉底、眼…

(三)行为模式:11、模板模式(Template Pattern)(C++示例)

目录 1、模板模式含义 2、模板模式的UML图学习 3、模板模式的应用场景 4、模板模式的优缺点 5、C实现的实例 1、模板模式含义 模板模式&#xff08;Template Method Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一个操作中的算法骨架&#xff0c;将某些步骤…

快速学习:Linux中传输文件夹的10个scp命令

本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法&#xff0c;涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景&#xff0c;旨在帮助用户在不同情…

设计模式(五)原型模式详解

设计模式&#xff08;五&#xff09;原型模式详解 原型模型简介 定义&#xff1a;原型模型是一种创建型设计模型&#xff0c;它允许通过克隆/复制现有对象来创建新的对象&#xff0c;而无需通过常规的构造函数进行实例化。 这种方式的主要优势是在运行时不需要知道具体的类&a…

【C++单调栈】962. 最大宽度坡|1607

本文涉及的基础知识点 C单调栈 LeetCode962. 最大宽度坡 给定一个整数数组 A&#xff0c;坡是元组 (i, j)&#xff0c;其中 i < j 且 A[i] < A[j]。这样的坡的宽度为 j - i。 找出 A 中的坡的最大宽度&#xff0c;如果不存在&#xff0c;返回 0 。 示例 1&#xff1a;…

免费的GB28181设备端EasyGBD,支持国标GB28181-2016和国标GB28181-2022,支持各种ARM系统、国产芯片、信创芯片

现在市面上还有很多卖国标GB28181设备端SDK的&#xff0c;EasyGBD免费的&#xff0c;基于C/C开发的国标GB28181设备库。 技术规格 编程语言&#xff1a;C/C&#xff0c;能适配任何平台&#xff0c;包括但不限于Windows、Linux、Android、ARM视频编解码器&#xff1a;H264、H26…

MPL助力固态前驱体光刻胶,图案化金属氧化物半导体更精密

大家好&#xff01;今天来了解一项金属氧化物增材制造研究——《Ultra-high precision nano additive manufacturing of metal oxide semiconductors via multi-photon lithography》发表于《nature communications》。在现代电子信息领域&#xff0c;金属氧化物半导体至关重要…

【031】基于SpringBoot+Vue实现的在线考试系统

文章目录 系统说明技术选型成果展示账号地址及其他说明源码获取 系统说明 基于SpringBootVue实现的在线考试系统是为高校打造的一款在线考试平台。 系统功能说明 1、系统共有管理员、老师、学生三个角色&#xff0c;管理员拥有系统最高权限。 2、老师拥有考试管理、题库管理…

mpu6050 设置低功耗模式

mpu6050 电源管理寄存器&#xff0c;参考博客&#xff1a;MPU6050学习笔记&#xff08;电源管理器1、2&#xff09; - LivingTowardTheSun - 博客园 (cnblogs.com) 根据手册&#xff0c;设置对应的寄存器即可&#xff1b; 具体代码&#xff1a; #define MPU_PWR_MGMT1_REG …

111.SAP ABAP - Function ALV - 列、行、单元格颜色 - 记录

目录 1.介绍 2.列背景色 3.行背景色 4.单元格背景色 4.1颜色码相关的结构 LVC_T_SCOL LVC_S_SCOL LVC_S_COLO 4.2单元格颜色设置方法 5.ALV 颜色码 1.介绍 在数据展示方面&#xff0c;要求ALV的数据列、行、单元格通过颜色醒目显示&#xff08;颜色展示…