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.rhkb.cn/news/12773.html

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

相关文章

manimgl安装

一、环境 笔记本 $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.5 LTS Release: 22.04 Codename: jammy二、安装miniconda3 manimgl基于python开发&#xff0c;为了防止将笔记本中已有的python环境破坏&#xff0c;因此…

c++中priority_queue的应用及模拟实现

1.介绍 priority_queue 是一种数据结构&#xff0c;它允许你以特定的顺序存储和访问元素。在 C 标准模板库&#xff08;STL&#xff09;中&#xff0c;priority_queue 是一个基于容器适配器的类模板&#xff0c;它默认使用 std::vector 作为底层容器&#xff0c;并且默认使用最…

【技术追踪】DiffMIC:用于医学图像分类的双引导扩散网络(MICCAI-2024)

似乎是第一个用于医学图像分类的扩散模型嗷~ 论文&#xff1a;DiffMIC: Dual-Guidance Diffusion Network for Medical Image Classification 代码&#xff1a;https://github.com/scott-yjyang/DiffMIC 0、摘要 扩散概率模型最近在生成式图像建模中表现出了显著的性能&#xf…

Deepseek v3R1 学习笔记

o1 o1 模型在训练过程中混合了多种奖励函数的设计方法&#xff0c;并且尝试从结果监督转向过程监督&#xff0c;在中间过程进行打分 使用的搜索策略&#xff1a;基于树的搜索和基于顺序修改的搜索 R1 R1-Zero 是从基础模型开始&#xff0c;完全由强化学习驱动&#xff0c;不…

技术书籍写作与编辑沟通指南

引言 撰写技术书籍不仅仅是知识的输出过程&#xff0c;更是与编辑团队紧密合作的协同工作。优秀的技术书籍不仅依赖作者深厚的技术背景&#xff0c;还需要精准的表达、流畅的结构以及符合出版要求的编辑润色。因此&#xff0c;如何高效地与编辑沟通&#xff0c;确保书籍质量&a…

DeepSeek+Ollama+AnythingLLM 本地部署完全指南,打造专属知识库

DeepSeekOllamaAnythingLLM 本地部署完全指南&#xff0c;打造专属知识库 1 Ollama 本地化部署DeepSeek R1 Ollama 是一个用于本地运行大语言模型&#xff08;LLMs&#xff09;的开源工具&#xff0c;提供简单的界面和优化的推理引擎 &#xff0c;使用户能够在个人设备上高效…

更换IP属地会影响网络连接速度吗

在数字化时代&#xff0c;网络连接速度对于个人用户和企业来说都至关重要。无论是日常浏览网页、观看视频&#xff0c;还是进行在线办公、游戏娱乐&#xff0c;网络速度都直接影响着我们的体验。而IP属地&#xff0c;作为网络连接中的一个重要元素&#xff0c;其变动是否会引发…

2025 持续防范 GitHub 投毒,通过 Sharp4SuoExplorer 分析 Visual Studio 隐藏文件

在2024年底的网络安全事件中&#xff0c;某提权工具被发现植入后门&#xff0c;攻击者利用 .suo 文件作为隐蔽的攻击方式。由于 .suo 文件是 Visual Studio 项目的隐藏配置文件&#xff0c;通常不为安全研究人员所关注&#xff0c;因此为攻击者提供了潜在的攻击渠道。 初步调查…

每日Attention学习19——Convolutional Multi-Focal Attention

每日Attention学习19——Convolutional Multi-Focal Attention 模块出处 [ICLR 25 Submission] [link] UltraLightUNet: Rethinking U-shaped Network with Multi-kernel Lightweight Convolutions for Medical Image Segmentation 模块名称 Convolutional Multi-Focal Atte…

【自然语言处理(NLP)】NLP实战:IMDB影评情感分析项目

文章目录 介绍IMDB影评情感分析项目数据集项目实现1. 导包2. 加载IMDB数据3. 查看部分数据4. 分词5. 加载数据整合6. 构建模型7. 词嵌入8. 初始化模型和权重9. glove词向量10. 训练和评估11. 预测 个人主页&#xff1a;道友老李 欢迎加入社区&#xff1a;道友老李的学习社区 介…

企业高效管理策略中的关键一环:WorkWin 监控上网时间的软件的效能剖析

在企业日常运营体系中&#xff0c;员工工作效率与网络资源的合理配置&#xff0c;始终是企业管理者重点关注的核心议题。伴随互联网的广泛普及&#xff0c;员工在工作时段内的网络使用行为日益常态化。然而&#xff0c;若缺乏行之有效的上网时间管控机制&#xff0c;极易导致员…

Spring AI 智能体通过 MCP 集成本地文件数据

作者&#xff1a;刘军 Model Context Protocol&#xff08;MCP&#xff09;简介 模型上下文协议&#xff08;即 Model Context Protocol&#xff0c;MCP&#xff09; [ 1] 是一个开放协议&#xff0c;它规范了应用程序如何向大型语言模型&#xff08;LLM&#xff09;提供上下…

DIY Shell:探秘进程构建与命令解析的核心原理

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言&#xff1a; Shell&#xff08;外壳&#xff09;是一个操作系统的用户界面&#xff0c;它提供了一种方式&#xff0c;使得用户能够与操作系统进行交互。Shell 是用户与操作系统之间的桥梁&#xff0c;允许用户通过命令行…

新春贺岁,共赴AGI之旅

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 往期精彩文章推荐 季姮教授独家文字版干货 | 面向知识渊博的大语言模型 关于AI TIME AI TIME源起于2019年&#xff0c;旨在发扬科学思辨精神&#xff0c;邀请各界人士对人工智能理论、算法和场景应用的本质问题…

FastAPI之参数传递和参数校验

FastAPI之参数传递 一、请求URL传参1、URL传参2、一个参数名&#xff0c;多个值3、参数校验3.1、默认值设置&#xff0c;和参数接口描述3.2、字符串长度校验3.3、正则表达式校验3.4、数值大小校验 二、请求体传参1、请求体单个传参 一、请求URL传参 1、URL传参 url请求参数是…

Vue Dom截图插件,截图转Base64 html2canvas

安装插件 npm install html2canvas --save插件使用 <template><div style"padding: 10px;"><div ref"imageTofile" class"box">发生什么事了</div><button click"toImage" style"margin: 10px;&quo…

C语言:深入了解指针3

1.回调函数是什么&#xff1f; 基本概念 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另⼀个函数&#xff0c;当这个指针被⽤来调⽤其所指向的函数 时&#xff0c;被调⽤的函数就是回调函数。回调函数不是由该函…

llama.cpp GGUF 模型格式

llama.cpp GGUF 模型格式 1. Specification1.1. GGUF Naming Convention (命名规则)1.1.1. Validating Above Naming Convention 1.2. File Structure 2. Standardized key-value pairs2.1. General2.1.1. Required2.1.2. General metadata2.1.3. Source metadata 2.2. LLM2.2.…

【C++】STL——vector底层实现

目录 &#x1f495; 1.vector三个核心 &#x1f495;2.begin函数&#xff0c;end函数的实现&#xff08;简单略讲&#xff09; &#x1f495;3.size函数&#xff0c;capacity函数的实现 &#xff08;简单略讲&#xff09; &#x1f495;4.reserve函数实现 &#xff08;细节…

Pinia状态管理

1、为什么要使用Pinia&#xff1f; Pinia 是 Vue 的存储库&#xff0c;它允许跨组件/页面共享状态 Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子&#xff0c;结合了 Vuex 5 核心团队讨论中的许多想法。最终&#xff0c;我们意识到 Pinia 已经实现了我们在 Vuex 5 中想…