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. 关于 == 运算符的运算规则:
== 永远只有一个运算规则,永远比较的是变量中保存的值之间的比较。
只不过有的时候这个值是基本数据类型。有的时候这个值是对象的内存地址。
⑴基本数据类型:
当==
用于比较两个基本数据类型(如int
、byte
、char
、long
、float
、double
、boolean
)时,它直接比较的是它们的值。例如:
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;
}
关键特性
-
内容比较
equals
方法比较的是字符串的实际字符内容,而非对象的内存地址。例如:String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1.equals(s2)); // true(内容相同) System.out.println(s1 == s2); // false(内存地址不同)
-
效率优化
-
先检查是否是同一个对象(
this == anObject
),直接返回true
。 -
比较长度是否相同,长度不同直接返回
false
。
-
-
字符级比较
对字符串的每个字符进行逐一比对,确保所有字符完全一致。
⑵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
方法,以保证相等的对象具有相同的哈希码,这是为了满足一些集合类(如HashMap
、HashSet
)的内部逻辑要求。
四、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()
方法进行对象克隆,被克隆的类必须满足以下两个条件:
- 该类必须实现
java.lang.Cloneable
接口,Cloneable
是一个标记接口,它本身不包含任何方法,只是用于告诉 Java 虚拟机该类可以被克隆。 - 通常需要在该类中重写
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
仍为20
,copyUser.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);}
}
运行结果为: