Java中的object类

1.Object类是什么?
🟪Object 是 Java 类库中的一个特殊类,也是所有类的父类(超类),位于类继承层次结构的顶端。也就是说,Java 允许把任何类型的对象赋给 Object 类型的变量。

🟦Java里面除了Object类,所有的类存在继承关系的。

🟩Object 类位于 java.lang 包中,编译时会自动导入, 当一个类被定义后,如果没有指定继承的父类,那么默认父类就是 Object 类。

Object类

①java.lang.Object是所有类的超类。java中所有类都实现了这个类中的方法。

②Object类是我们学习JDK类库的第一个类。通过这个类的学习要求掌握会查阅API帮助文档。

③现阶段Object类中需要掌握的方法:

       ● toString:将java对象转换成字符串。

       ● equals:判断两个对象是否相等。

④现阶段Object类中需要了解的方法:

      ●  hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。  

      ●  hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象。

      ●  finalize:当java对象被回收时,由GC自动调用被回收对象的finalize方法,通常在该方法中完成销毁前的准备。

      ●  clone:对象的拷贝。(浅拷贝,深拷贝)

                    protected修饰的只能在同一个包下或者子类中访问。

                    只有实现了Cloneable接口的对象才能被克隆。

一、toString方法

 Object类中的toString()方法:1. Object类设计toString()方法的目的是什么?这个方法的作用是:将java对象转换成字符串的表示形式。
2. Object类中toString()方法的默认实现是怎样的?
 public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}
 默认实现是:完整类名 + @ + 十六进制的数字这个输出结果可以等同看做一个java对象的内存地址。

例如:

Date类:

public class Date {private int year;private int month;private int day;public Date() {this(1970,1,1);}public Date(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}public int getYear() {return year;}public void setYear(int year) {this.year = year;}public int getMonth() {return month;}public void setMonth(int month) {this.month = month;}public int getDay() {return day;}public void setDay(int day) {this.day = day;}
}

DateTest测试类:


public class DateTest {public static void main(String[] args) {DateTest dateTest = new DateTest();//创建DateTest对象String s = dateTest.toString();System.out.println(s);Date d = new Date();//创建Date对象String s1 = d.toString();System.out.println(s1);}
}

运行结果:

  • 功能:该方法用于将 Java 对象转换为字符串表示形式。默认情况下,Object类的toString方法返回的字符串格式为类名@十六进制哈希码,例如com.example.MyClass@12345678。但在实际应用中,通常会在自定义类中重写toString方法,以便返回更有意义的对象信息,比如对象的属性值等。

例如:在Date类中重写toString方法

 @Overridepublic String toString() {return this.year + "年" + this.month + "月" + this.day + "日";}

运行结果:

注意点:当println()输出的是一个引用的时候,会自动调用“引用.toString()”

Date类:

public class Date {private int year;private int month;private int day;public Date() {this(1970,1,1);}public Date(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}public int getYear() {return year;}public void setYear(int year) {this.year = year;}public int getMonth() {return month;}public void setMonth(int month) {this.month = month;}public int getDay() {return day;}public void setDay(int day) {this.day = day;}
}

DateTest测试类:

public class DateTest {public static void main(String[] args) {DateTest dateTest = new DateTest();//创建DateTest对象String s = dateTest.toString();System.out.println(s);Date d = new Date();//创建Date对象String s1 = d.toString();System.out.println(s1);Date d3 = new Date(2008, 5, 12);// 当println()输出的是一个引用的时候,会自动调用“引用.toString()”System.out.println(d3); // 2008年5月12日System.out.println(d3.toString()); // 2008年5月12日}
}

运行结果:

  Date d4 = null;System.out.println(d4); // "null"System.out.println(d4 == null ? "null" : d4.toString());//System.out.println(d4.toString()); // 空指针异常。

运行结果:

在 Java 中,当你使用 System.out.println() 方法输出一个引用类型的对象时,会自动调用该对象的 toString() 方法,下面我们从源码层面来剖析其原因。

1. System.out 的本质

在 Java 里,System.out 是 PrintStream 类的一个实例,它是标准输出流。System 类中有如下静态初始化代码块来初始化 out 变量:

public final class System {// ... 其他代码 ...public final static PrintStream out = null;static {// 初始化 out 为标准输出流setOut0(new PrintStream(new BufferedOutputStream(FileOutputStreamDescriptor.out), true));}// ... 其他代码 ...
}

2. println() 方法调用流程

当你调用 System.out.println(Object x) 方法时,会进入 PrintStream 类中的相应 println 方法。以下是 PrintStream 类中 println(Object x) 方法的源码:

public class PrintStream extends FilterOutputStream implements Appendable, Closeable {// ... 其他代码 ...public void println(Object x) {String s = String.valueOf(x);synchronized (this) {print(s);newLine();}}// ... 其他代码 ...
}

在这个方法中,它首先调用了 String.valueOf(x) 方法将传入的对象 x 转换为字符串 s,然后将这个字符串打印出来并换行。

3. String.valueOf(Object obj) 方法

接下来看 String.valueOf(Object obj) 方法的源码,它位于 String 类中:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {// ... 其他代码 ...public static String valueOf(Object obj) {return (obj == null) ? "null" : obj.toString();}// ... 其他代码 ...
}

从这段源码可以看出,String.valueOf(Object obj) 方法会先判断传入的对象 obj 是否为 null,如果是 null 则返回字符串 "null";如果不是 null,则直接调用该对象的 toString() 方法。

总结

综上所述,当你使用 System.out.println() 方法输出一个引用类型的对象时,println 方法内部会调用 String.valueOf() 方法将对象转换为字符串,而 String.valueOf() 方法又会去调用对象的 toString() 方法(对象不为 null 的情况下),所以最终就实现了自动调用对象的 toString() 方法来输出对象的字符串表示形式。这就是为什么在 Java 中使用 println() 输出引用时会自动调用 引用.toString() 的原因。


二、equals方法

 Object类中的equals方法:

1. Object类设计equals方法的作用是什么?目的是什么?

①equals方法的作用是:判断两个对象是否相等。

②equals方法的返回值是true/false 

③true代表两个对象相等。

④false代表两个对象不相等。


2. Object类中对equals方法的默认实现是怎样的?

public boolean equals(Object obj) {return (this == obj);}

 a.equals(b) 表面是a和b的比较。实际上方法体当中是:this和obj的比较


3. 关于 == 运算符的运算规则:

 == 永远只有一个运算规则,永远比较的是变量中保存的值之间的比较。     

只不过有的时候这个值是基本数据类型。有的时候这个值是对象的内存地址。

⑴基本数据类型

==用于比较两个基本数据类型(如intbytecharlongfloatdoubleboolean)时,它直接比较的是它们的值。例如:

int num1 = 5;
int num2 = 5;
System.out.println(num1 == num2); // 输出 true

⑵引用数据类型

对于引用数据类型(如类、接口、数组等),==比较的是两个变量所保存的对象的内存地址。也就是说,如果两个引用变量指向同一个对象,那么==比较的结果为true;否则为false。例如:

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // 输出 false,因为str1和str2指向不同的对象

⑶而在 Java 中,要比较两个对象的内容是否相等,通常需要使用对象的equals方法(前提是该类已经重写了equals方法)。例如,对于String类,equals方法被重写来比较字符串的内容:

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // 输出 true

总结来说,==运算符在基本数据类型和引用数据类型的比较行为上有所不同,使用时需要注意。在比较对象内容时,通常使用equals方法更为合适。


4. equals方法为什么要重写? 

因为Object类中的equals方法在进行比较的时候,比较的是两个java对象的内存地址。

我们希望比较的是对象的内容。只要对象的内容相等,则认为是相同的。


5.注意点

字符串的比较不能使用 ==,必须使用equals方法进行比较。

⑴字符串String类型已经重写了equals方法。

在Java中,String类确实重写了Object类的equals方法,用于比较两个字符串对象的内容是否相等,而不是比较对象的内存地址(即==的比较逻辑)。这是字符串操作中非常关键的特性,也是String类设计的核心之一。


String类的equals方法源码解析

String类中的equals方法重写如下:

public boolean equals(Object anObject) {if (this == anObject) { // 1. 先检查是否是同一个对象(内存地址相同)return true;}if (anObject instanceof String) { // 2. 检查是否为String类型String anotherString = (String) anObject;int n = value.length;if (n == anotherString.value.length) { // 3. 比较长度是否一致char v1[] = value; // 当前字符串的字符数组char v2[] = anotherString.value; // 目标字符串的字符数组int i = 0;while (n-- != 0) { // 4. 逐个字符比较if (v1[i] != v2[i])return false;i++;}return true;}}return false;
}

关键特性

  1. 内容比较
    equals方法比较的是字符串的实际字符内容,而非对象的内存地址。例如:

    String s1 = new String("hello");
    String s2 = new String("hello");
    System.out.println(s1.equals(s2)); // true(内容相同)
    System.out.println(s1 == s2);      // false(内存地址不同)
  2. 效率优化

    • 先检查是否是同一个对象(this == anObject),直接返回true

    • 比较长度是否相同,长度不同直接返回false

  3. 字符级比较
    对字符串的每个字符进行逐一比对,确保所有字符完全一致。

⑵equals方法重写需要“彻底”重写。

Address类:

public class Address {private String city;private String street;public Address() {}public Address(String city, String street) {this.city = city;this.street = street;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public String getStreet() {return street;}public void setStreet(String street) {this.street = street;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +", street='" + street + '\'' +'}';}@Overridepublic boolean equals(Object obj) {if(obj == null) return false;if(this == obj) return true;if(obj instanceof Address){Address a = (Address) obj;return this.city.equals(a.city) && this.street.equals(a.street);}return false;}
}

User类:

public class User {private String name;private Address address;//对象引用public User() {}public User(String name, Address address) {this.name = name;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", address=" + address +'}';}@Overridepublic boolean equals(Object obj) {// u.equals(u2)// this就是u  obj就是u2if(obj == null) return false;if(this == obj) return true;if(obj instanceof User){User user = (User) obj;if(this.name.equals(user.name)  && this.address.equals(user.address)){return true;}}return false;}
}

Test类:

public class Test {public static void main(String[] args) {// 创建家庭住址对象//得先创建家庭住址对象,才能创建用户对象Address a = new Address("北京", "大兴");// 创建用户对象User u = new User("张三", a);// 创建家庭住址对象2Address a2 = new Address("北京", "大兴");// 创建用户对象2User u2 = new User("张三", a2);//输出false,虽然u和u2内容一样,但它们的内存地址不一样System.out.println(u.equals(u2));}
}

运行结果:

“彻底” 重写的理解

      “彻底” 重写 equals 方法要求在比较对象时,不仅要比较当前类的属性,还要递归地比较所有引用类型的属性。在 User 类中,address 是一个引用类型的属性,如果 Address 类没有重写 equals 方法,那么在 User 类的 equals 方法中比较 address 属性时,默认会使用 Object 类的 equals 方法,即比较引用是否相等,而不是比较 address 对象的内容是否相等。这样即使两个 User 对象的 name 和 address 的内容都相同,但由于 address 对象的引用不同,equals 方法仍然会返回 false,这显然不符合我们的预期。

       因此,为了确保 equals 方法能够准确地比较两个对象的内容是否相等,我们需要在每个包含引用类型属性的类中重写 equals 方法,并且在比较引用类型属性时,调用该属性所属类重写的 equals 方法,从而实现 “彻底” 重写。

三、hashCode()方法

关于Object类的hashCode()方法:
*      hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。
*      Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。
*      hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象

* * hashCode()方法在Object类中的默认实现: 这是一个本地方法,底层调用了C++写的动态链接库程序:xxx.dll

示例1:

public class Test01 {public static void main(String[] args) {Test01 t = new Test01();int i = t.hashCode();System.out.println(i); //随机输出哈希码Test01 t2 = new Test01();int i1 = t2.hashCode();System.out.println(i1);System.out.println(new Object().hashCode());System.out.println(new Object().hashCode());System.out.println(new Object().hashCode());System.out.println(new Object().hashCode());System.out.println(new Object().hashCode());System.out.println(new Object().hashCode());}
}

运行结果:

  • 功能描述:返回该对象的哈希码值。哈希码主要用于在哈希表等数据结构中快速定位和存储对象。在Object类中,默认的哈希码是根据对象的内存地址生成的。但如果重写了equals方法,通常也需要重写hashCode方法,以保证相等的对象具有相同的哈希码,这是为了满足一些集合类(如HashMapHashSet)的内部逻辑要求。

四、finalize()方法

关于Object类中的finalize()方法:
*      finalize:当java对象被回收时,由GC自动调用被回收对象的finalize方法,通常在该方法中完成销毁前的准备
*      从Java9开始,这个方法被标记已过时,不建议使用。作为了解。

* 在Object类中是这样实现的:很显然,这个方法是需要子类重写的。

protected void finalize() throws Throwable { }
  • 功能描述:当垃圾回收器确定不存在对该对象的更多引用时,由垃圾回收器在回收对象之前调用此方法。在这个方法中,可以进行一些资源释放、清理等操作,但不建议过度依赖这个方法,因为垃圾回收的时机是不确定的,而且在 Java 9 及以后的版本中,finalize方法已被标记为@Deprecated,逐渐不推荐使用。

回收对象的例子:

Person类:


public class Person {@Overrideprotected void finalize() throws Throwable {System.out.println(this + "即将被回收");}
}

测试类Test02:

public class Test02 {public static void main(String[] args) {for (int i = 0; i < 10000; i++) {//垃圾到一定程度才会调用垃圾回收器Person p1 = new Person();p1 = null;   //p1等于null,即为垃圾对象// 建议启动垃圾回收器(这只是建议启动垃圾回收器)if(i % 1000 == 0){System.gc();//System里的gc()方法就是启动垃圾回收器}}}
}

运行结果:

五、clone()方法

1.浅拷贝(Shallow Copy)

定义

浅拷贝会创建一个新对象,新对象的基本数据类型属性会复制一份新的值,而对于引用数据类型的属性,仅仅复制其引用,也就是新对象和原对象的引用类型属性会指向同一个内存地址。这意味着如果通过新对象修改引用类型属性所指向的对象内容,原对象的该属性内容也会被修改。

实现条件

在 Java 中,要实现浅拷贝,类需要满足以下条件:

  • 实现 Cloneable 接口。
  • 重写 Object 类的 clone() 方法。

User类:

public class User {private int age;public User() {}public User(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"age=" + age +'}';}}

诺添加以下代码

当你在 Java 代码中遇到 Unhandled exception: java.lang.CloneNotSupportedException 错误时,这通常是因为你调用了 clone() 方法,但没有对可能抛出的 CloneNotSupportedException 异常进行处理。下面为你详细分析该异常出现的原因以及解决办法。

异常原因

在 Java 里,clone() 方法定义在 Object 类中,其签名为 protected native Object clone() throws CloneNotSupportedException。这表明 clone() 方法可能会抛出 CloneNotSupportedException 异常。如果一个类没有实现 Cloneable 接口却调用了 clone() 方法,或者调用 clone() 方法时没有对 CloneNotSupportedException 进行处理,就会产生这个编译错误。

解决办法

1. 实现 Cloneable 接口

要使用 clone() 方法,类必须实现 Cloneable 接口。Cloneable 接口是一个标记接口,本身不包含任何方法,它只是告诉 Java 虚拟机该类可以被克隆。

2. 处理 CloneNotSupportedException 异常

可以使用 try-catch 块捕获该异常,或者在方法签名中使用 throws 关键字声明抛出该异常。

  public void Test() throws CloneNotSupportedException {this.clone();}
UserTest类
public class UserTest {public static void main(String[] args) {//创建User对象User user=new User(20);//克隆一个user对象user.clone();}
}
// 克隆一个user对象
// 报错原因:因为Object类中的clone()方法是protected修饰的。
// protected修饰的只能在:本类,同包,子类中访问。

Object 类中的 clone() 方法定义如下:

protected native Object clone() throws CloneNotSupportedException;

怎么解决clone()方法的调用问题?

在子类中重写该clone()方法。

   为了保证clone()方法在任何位置都可以调用,建议将其修饰符修改为:public

代码如下:

User类:

public class User {private int age;public User() {}public User(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"age=" + age +'}';}/*  public void Test() throws CloneNotSupportedException {this.clone();}*/@Overridepublic Object clone() throws CloneNotSupportedException{return super.clone();}
}

UserTest类:

public class UserTest {public static void main(String[] args) throws CloneNotSupportedException {//创建User对象User user=new User(20);//克隆一个user对象Object obj=user.clone();//克隆出的新对象,用Object去接收}
}

运行结果:

凡事参加克隆的对象,必须实现一个标志接口:java.lang.Cloneable *      

java中接口包括两大类:  一类是:起到标志的作用,标志型接口。 

 另一类是:普通接口。

当你遇到 java.lang.CloneNotSupportedException 异常时,这表明你尝试克隆一个不支持克隆操作的对象。在 Java 中,要使用 clone() 方法进行对象克隆,被克隆的类必须满足以下两个条件:

  1. 该类必须实现 java.lang.Cloneable 接口,Cloneable 是一个标记接口,它本身不包含任何方法,只是用于告诉 Java 虚拟机该类可以被克隆。
  2. 通常需要在该类中重写 Object 类的 clone() 方法。

异常原因分析

根据你给出的异常堆栈信息 Exception in thread "main" java.lang.CloneNotSupportedException: lianxi.oop29.User,可以知道问题出在 lianxi.oop29.User 类上,该类可能没有实现 Cloneable 接口,从而导致调用 clone() 方法时抛出 CloneNotSupportedException 异常。

User类:

public class User implements Cloneable{//打个标记,该类被克隆了private int age;public User() {}public User(int age) {this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"age=" + age +'}';}/*  public void Test() throws CloneNotSupportedException {this.clone();}*/@Overridepublic Object clone() throws CloneNotSupportedException{return super.clone();}
}

UserTest类:

public class UserTest {public static void main(String[] args) throws CloneNotSupportedException {//创建User对象User user=new User(20);//克隆一个user对象Object obj=user.clone();//克隆出的新对象,用Object去接收System.out.println(user);// 修改克隆之后的对象的age属性User copyUser = (User) obj;copyUser.setAge(100);System.out.println("克隆之后的新对象的年龄:" + copyUser.getAge());System.out.println("原始对象的年龄:" + user.getAge());}
}

运行结果:

  • 浅拷贝的定义:对于基本数据类型字段直接复制值,对于引用类型字段仅复制引用地址(共享同一对象)。

  • 代码实现

    • User 类实现了 Cloneable 接口,并调用 super.clone()

    • 由于 age 是 int(基本数据类型),拷贝时会直接复制值,因此修改拷贝对象的 age 不会影响原始对象。

    • 若 User 类中存在引用类型字段(例如 Address 对象),浅拷贝会导致原始对象和拷贝对象共享该引用对象(修改一处会影响另一处)。


 为什么输出结果中 age 不同?

  • 基本数据类型的特点int 的值直接存储在对象内存中,浅拷贝会直接复制该值到新对象。

  • 代码逻辑验证

    User user = new User(20);        // 原始对象 age=20
    User copyUser = (User) user.clone(); // 拷贝对象 age=20(独立值)
    copyUser.setAge(100);            // 仅修改拷贝对象的 age
    • 最终 user.age 仍为 20copyUser.age 变为 100


 如果 User 类包含引用类型字段,浅拷贝会如何?

假设 User 类中添加引用类型字段 Address

class User implements Cloneable {private int age;private Address address;  // 引用类型字段// 省略其他代码...@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone(); // 浅拷贝:address字段共享同一对象}
}class Address {private String city;// 省略 getter/setter...
}
  • 验证代码

    User user = new User(20, new Address("Beijing"));
    User copyUser = (User) user.clone();copyUser.getAddress().setCity("Shanghai"); 
    System.out.println(user.getAddress().getCity()); // 输出 "Shanghai"(被修改)
    • 浅拷贝的副作用:原始对象和拷贝对象共享 address,修改一处会影响另一处。

浅拷贝内存图

Address类:

public class Address {private String city;private String street;public Address() {}public Address(String city, String street) {this.city = city;this.street = street;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public String getStreet() {return street;}public void setStreet(String street) {this.street = street;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +", street='" + street + '\'' +'}';}
}

User类:

public class User implements Cloneable{private String name;private Address addr;public User() {}public User(String name, Address addr) {this.name = name;this.addr = addr;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddr() {return addr;}public void setAddr(Address addr) {this.addr = addr;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", addr=" + addr +'}';}@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}
}

测试类Test:

public class Test {public static void main(String[] args) throws CloneNotSupportedException {//创建住址对象Address a=new Address("北京","海淀");//创建User对象User user1=new User("李四",a);User user2=(User) user1.clone();//克隆一个User对象因为返回的是Object对象,所以需要转型System.out.println(user1);System.out.println(user2);user2.getAddr().setCity("天津");System.out.println("===================");System.out.println(user1);System.out.println(user2);}
}

运行结果:

jvm图

解释:

     克隆时,只克隆了User类型对象,Address没有一起克隆

     当修改city为“天津”时,city=ox11指向“天津”,因为User1和user2同时指向Address,所以同时修改为“天津”

      当引用数据类型需要克隆时(即Addr),则为深克隆

2.深拷贝(Deep Copy)

定义

深拷贝会创建一个新对象,新对象的所有属性,包括基本数据类型和引用数据类型,都会被复制一份新的值。这意味着新对象和原对象在内存中是完全独立的,修改新对象的任何属性都不会影响原对象。

实现方式

实现深拷贝有多种方式,常见的有手动实现和使用序列化与反序列化。

手动实现深拷贝

手动实现深拷贝需要在 clone() 方法中递归地复制引用类型的属性。

实现代码:

Address类:

public class Address {private String city;private String street;public Address() {}public Address(String city, String street) {this.city = city;this.street = street;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public String getStreet() {return street;}public void setStreet(String street) {this.street = street;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +", street='" + street + '\'' +'}';}
}

User类:

public class User implements Cloneable{private String name;private Address addr;public User() {}public User(String name, Address addr) {this.name = name;this.addr = addr;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddr() {return addr;}public void setAddr(Address addr) {this.addr = addr;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", addr=" + addr +'}';}
/*@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}*/@Overridepublic Object clone() throws CloneNotSupportedException {// 重写方法,让其达到深克隆的效果。// User要克隆,User对象关联的Address对象也需要克隆一份。Address copyAddr = (Address)this.getAddr().clone();//克隆一份AddrUser copyUser = (User)super.clone();//克隆一份UsercopyUser.setAddr(copyAddr);   //把之前克隆的Addr的地址给新Userreturn copyUser;}
}

但是注意:克隆Address,Address也要重写clone()方法,不然会报错

步骤:1.Address类添加克隆标识

           2.重写clone()方法

           3.protected修改为public

所以完整代码为:

Address类:

public class Address implements Cloneable{private String city;private String street;public Address() {}public Address(String city, String street) {this.city = city;this.street = street;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public String getStreet() {return street;}public void setStreet(String street) {this.street = street;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +", street='" + street + '\'' +'}';}@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}
}

User类:

public class User implements Cloneable{private String name;private Address addr;public User() {}public User(String name, Address addr) {this.name = name;this.addr = addr;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddr() {return addr;}public void setAddr(Address addr) {this.addr = addr;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", addr=" + addr +'}';}
/*@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}*/@Overridepublic Object clone() throws CloneNotSupportedException {// 重写方法,让其达到深克隆的效果。// User要克隆,User对象关联的Address对象也需要克隆一份。Address copyAddr = (Address)this.getAddr().clone();//克隆一份AddrUser copyUser = (User)super.clone();//克隆一份UsercopyUser.setAddr(copyAddr);   //把之前克隆的Addr的地址给新Userreturn copyUser;}
}

测试类Test:

public class Test {public static void main(String[] args) throws CloneNotSupportedException {//创建住址对象Address a=new Address("北京","海淀");//创建User对象User user1=new User("李四",a);User user2=(User) user1.clone();//克隆一个User对象因为返回的是Object对象,所以需要转型System.out.println(user1);System.out.println(user2);user2.getAddr().setCity("天津");System.out.println("===================");System.out.println(user1);System.out.println(user2);}
}

运行结果为:

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

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

相关文章

uniapp小程序自定义中间凸起样式底部tabbar

我自己写的自定义的tabbar效果图 废话少说咱们直接上代码&#xff0c;一步一步来 第一步&#xff1a; 找到根目录下的 pages.json 文件&#xff0c;在 tabBar 中把 custom 设置为 true&#xff0c;默认值是 false。list 中设置自定义的相关信息&#xff0c; pagePath&#x…

四、GPIO中断实现按键功能

4.1 GPIO简介 输入输出&#xff08;I/O&#xff09;是一个非常重要的概念。I/O泛指所有类型的输入输出端口&#xff0c;包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO&#xff08;General-Purpose Input/Output&#xff09;则是一个常见的术语&#xff0c…

【Elasticsearch】post_filter

post_filter是 Elasticsearch 中的一种后置过滤机制&#xff0c;用于在查询执行完成后对结果进行过滤。以下是关于post_filter的详细介绍&#xff1a; 工作原理 • 查询后过滤&#xff1a;post_filter在查询执行完毕后对返回的文档集进行过滤。这意味着所有与查询匹配的文档都…

从零开始:用Qt开发一个功能强大的文本编辑器——WPS项目全解析

文章目录 引言项目功能介绍1. **文件操作**2. **文本编辑功能**3. **撤销与重做**4. **剪切、复制与粘贴**5. **文本查找与替换**6. **打印功能**7. **打印预览**8. **设置字体颜色**9. **设置字号**10. **设置字体**11. **左对齐**12. **右对齐**13. **居中对齐**14. **两侧对…

【IoCDI】_Spring的基本扫描机制

目录 1. 创建测试项目 2. 改变启动类所属包 3. 使用ComponentScan 4. Spring基本扫描机制 程序通过注解告诉Spring希望哪些bean被管理&#xff0c;但在仅使用Bean时已经发现&#xff0c;Spring需要根据五大类注解才能进一步扫描方法注解。 由此可见&#xff0c;Spring对注…

通向AGI之路:人工通用智能的技术演进与人类未来

文章目录 引言:当机器开始思考一、AGI的本质定义与技术演进1.1 从专用到通用:智能形态的范式转移1.2 AGI发展路线图二、突破AGI的五大技术路径2.1 神经符号整合(Neuro-Symbolic AI)2.2 世界模型架构(World Models)2.3 具身认知理论(Embodied Cognition)三、AGI安全:价…

【工具变量】中国省级八批自由贸易试验区设立及自贸区设立数据(2024-2009年)

一、测算方式&#xff1a;参考C刊《中国软科学》任晓怡老师&#xff08;2022&#xff09;的做法&#xff0c;使用自由贸易试验区(Treat Post) 表征&#xff0c;Treat为个体不随时间变化的虚拟变量&#xff0c;如果该城市设立自由贸易试验区则赋值为1&#xff0c;反之赋值为0&am…

Java进阶总结——集合

Java进阶总结——集合 说明&#xff1a;对于以上的框架图有如下几点说明 1.所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出&#xff1a;Collection和Map&#xff0c;Collection和Map是Java集合框架的根接口&#xff0c;这两个接口又包含了一些子接口或实…

计算机视觉和图像处理

计算机视觉与图像处理的最新进展 随着人工智能技术的飞速发展&#xff0c;计算机视觉和图像处理作为其中的重要分支&#xff0c;正逐步成为推动科技进步和产业升级的关键力量。 一、计算机视觉的最新进展 计算机视觉&#xff0c;作为人工智能的重要分支&#xff0c;主要研究如…

3.PPT:华老师-计算机基础课程【3】

目录 NO12​ NO34​ NO56​ NO789​ NO12 根据考生文件夹下的Word文档“PPT素材.docx”中提供的内容在PPT.pptx中生成初始的6张幻灯片 新建幻灯片6张→ctrlc复制→ctrlv粘贴开始→新建幻灯片→幻灯片(从大纲)→Word文档注❗前提是&#xff1a;Word文档必须应用标题1、标题2…

(三)QT——信号与槽机制——计数器程序

目录 前言 信号&#xff08;Signal&#xff09;与槽&#xff08;Slot&#xff09;的定义 一、系统自带的信号和槽 二、自定义信号和槽 三、信号和槽的扩展 四、Lambda 表达式 总结 前言 信号与槽机制是 Qt 中的一种重要的通信机制&#xff0c;用于不同对象之间的事件响…

基于多智能体强化学习的医疗AI中RAG系统程序架构优化研究

一、引言 1.1 研究背景与意义 在数智化医疗飞速发展的当下,医疗人工智能(AI)已成为提升医疗服务质量、优化医疗流程以及推动医学研究进步的关键力量。医疗 AI 借助机器学习、深度学习等先进技术,能够处理和分析海量的医疗数据,从而辅助医生进行疾病诊断、制定治疗方案以…

Redis --- 秒杀优化方案(阻塞队列+基于Stream流的消息队列)

下面是我们的秒杀流程&#xff1a; 对于正常的秒杀处理&#xff0c;我们需要多次查询数据库&#xff0c;会给数据库造成相当大的压力&#xff0c;这个时候我们需要加入缓存&#xff0c;进而缓解数据库压力。 在上面的图示中&#xff0c;我们可以将一条流水线的任务拆成两条流水…

使用 Ollama 和 Kibana 在本地为 RAG 测试 DeepSeek R1

作者&#xff1a;来自 Elastic Dave Erickson 及 Jakob Reiter 每个人都在谈论 DeepSeek R1&#xff0c;这是中国对冲基金 High-Flyer 的新大型语言模型。现在他们推出了一款功能强大、具有开放权重的思想链推理 LLM&#xff0c;这则新闻充满了对行业意味着什么的猜测。对于那些…

2025年大年初一篇,C#调用GPU并行计算推荐

C#调用GPU库的主要目的是利用GPU的并行计算能力&#xff0c;加速计算密集型任务&#xff0c;提高程序性能&#xff0c;支持大规模数据处理&#xff0c;优化资源利用&#xff0c;满足特定应用场景的需求&#xff0c;并提升用户体验。在需要处理大量并行数据或进行复杂计算的场景…

Unity 2D实战小游戏开发跳跳鸟 - 计分逻辑开发

上文对障碍物的碰撞逻辑进行了开发,接下来就是进行跳跳鸟成功穿越过障碍物进行计分的逻辑开发,同时将对应的分数以UI的形式显示告诉玩家。 计分逻辑 在跳跳鸟通过障碍物的一瞬间就进行一次计分,计分后会同步更新分数的UI显示来告知玩家当前获得的分数。 首先我们创建一个用…

langchain基础(二)

一、输出解析器&#xff08;Output Parser&#xff09; 作用&#xff1a;&#xff08;1&#xff09;让模型按照指定的格式输出&#xff1b; &#xff08;2&#xff09;解析模型输出&#xff0c;提取所需的信息 1、逗号分隔列表 CommaSeparatedListOutputParser&#xff1a;…

游戏AI,让AI 玩游戏有什么作用?

让 AI 玩游戏这件事远比我们想象的要早得多。追溯到 1948 年&#xff0c;图灵和同事钱伯恩共同设计了国际象棋程序 Turochamp。之所以设计这么个程序&#xff0c;图灵是想说明&#xff0c;机器理论上能模拟人脑能做的任何事情&#xff0c;包括下棋这样复杂的智力活动。 可惜的是…

鸿蒙物流项目之基础结构

目录&#xff1a; 1、项目结构2、三种包的区别和使用场景3、静态资源的导入4、颜色样式设置5、修改项目名称和图标6、静态包基础目录7、组件的抽离8、在功能模块包里面引用静态资源包的组件 1、项目结构 2、三种包的区别和使用场景 3、静态资源的导入 放在har包中&#xff0c;那…

51c视觉~CV~合集10

我自己的原文哦~ https://blog.51cto.com/whaosoft/13241694 一、CV创建自定义图像滤镜 热图滤镜 这组滤镜提供了各种不同的艺术和风格化光学图像捕捉方法。例如&#xff0c;热滤镜会将图像转换为“热图”&#xff0c;而卡通滤镜则提供生动的图像&#xff0c;这些图像看起来…