- Object.clone();
- 也叫克隆模式
- 想要new一个与原对象一样或者变化很小的对象可以使用原型模式,而不必手动new并且再次赋值了
UML类图
java中的原型模式
- 自带
- 实现原型模式需要实现标记型接口Cloneable
- 一般会重写clone()方法
- 如果重写clone()方法,而没有实现接口,调用时会报异常(因为clone方法是Object基类中的方法故可以重写)
- 一般用于一个对象的属性已经确定,需要产生很多相同对象的时候
- 需要区分深克隆与浅克隆
- 克隆实现的原理:拷贝内存(不调用构造方法,直接拷贝内存)
- 克隆方法必须重写:
- 因为Object中的clone是protected
- 并且是native方法
- 调用clone之后还要进行强制类型转换!
- 要完成克隆两部:
- 实现标记接口Cloneable
- 重写clone方法
浅克隆问题
/**
- 浅克隆 */
public class Test { public static void main(String[] args) throws Exception { Person p1 = new Person(); Person p2 = (Person)p1.clone(); System.out.println(p2.age + “ “ + p2.score); System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
}
}
class Person implements Cloneable { int age = 8; int score = 100;
Location loc = new Location("bj", 22);
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Location { String street; int roomNo;
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
}
- 将对象中的成员对象也进行clone,调用成员对象的clone方法
<a name="Zd4Fz"></a>
### 对Person对象进行深克隆===>同时将Location对象克隆
```java
package com.mashibing.dp.prototype.v2;
/**
* 深克隆的处理
*/
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
Person p2 = (Person)p1.clone();
System.out.println(p2.age + " " + p2.score);
System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
}
}
class Person implements Cloneable {
int age = 8;
int score = 100;
Location loc = new Location("bj", 22);
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person)super.clone();
// 对Person对象克隆的同时将Location对象克隆
p.loc = (Location)loc.clone();
return p;
}
}
class Location implements Cloneable {
String street;
int roomNo;
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
- Sting在克隆时需要深克隆吗?
- 不需要对String进行深克隆
- 因为String是直接指定的常量值,本来就是共用的
- 字符串常量池
- String是不可变类(String是不可变类,还是什么????Serializable可序列化的?这之间有什么关系)
- 虽然clone出来还是指向一个,但是改其中一个,另一个并不会变化(因为修改String默认指向另一个对象)
- new String()类似,引用指向堆中对象,队中对象本质上还是指向字符串常量池的常量(疑问?视频prototype原型2中为什么会说new String是需要深克隆,改变一个另一个 会改变)因为指向堆中的同一个对象
- 确实会改变,但一般代码中很少用new创建String,相当于每次都创建String,不符合实际使用,故用StringBuilder类型
- 图解
- v4版本在克隆Location的时候还要把Location中的StringBuilder给克隆了
- 在使用克隆的时候要考虑的比较多
String不需要进一步深克隆===>String是不可变类,重新赋值会指向另一个对象
```java package com.mashibing.dp.prototype.v3;
/**
String需要进一步深克隆吗? */ public class Test { public static void main(String[] args) throws Exception {
Person p1 = new Person();
Person p2 = (Person)p1.clone();
System.out.println(p2.age + " " + p2.score);
System.out.println(p2.loc);
System.out.println(p1.loc == p2.loc);
p1.loc.street = "sh";
System.out.println(p2.loc);
p1.loc.street.replace("sh", "sz");
System.out.println(p2.loc.street);
} }
class Person implements Cloneable { int age = 8; int score = 100;
Location loc = new Location("bj", 22);
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person)super.clone();
p.loc = (Location)loc.clone();
return p;
}
}
class Location implements Cloneable { String street; int roomNo;
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
public Location(String street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
<a name="zm9xt"></a>
### StringBuilder需要进一步深克隆
- StringBuilder对象是Person对象中的Location对象中的StringBuilder对象!!!
- 仍然需要深克隆
- 不进行深克隆的话,仍然是浅克隆(虽然Location对象是深克隆,但是StringBuilder仍然是同一个对象)
```java
package com.mashibing.dp.prototype.v4;
/**
* StringBuilder需要进一步深克隆吗?
*/
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
Person p2 = (Person)p1.clone();
System.out.println("p1.loc == p2.loc? " + (p1.loc == p2.loc));
p1.loc.street.reverse();
System.out.println(p2.loc.street);
}
}
class Person implements Cloneable {
int age = 8;
int score = 100;
Location loc = new Location(new StringBuilder("bj"), 22);
@Override
public Object clone() throws CloneNotSupportedException {
Person p = (Person)super.clone();
p.loc = (Location)loc.clone();
return p;
}
}
class Location implements Cloneable {
StringBuilder street;
int roomNo;
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", roomNo=" + roomNo +
'}';
}
public Location(StringBuilder street, int roomNo) {
this.street = street;
this.roomNo = roomNo;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
- 当遇到多层的对象的对象时(对象作为成员属性),要在每一层类的clone方法中显式调用对象属性的clone方法(对象属性所对应的类要**重写clone方法并实现Cloneable接口**)
总结
- 真正写程序的时候,clone方法极少用到,教学会讲到(用的少,问的少)
- 极少数的面试官会问到,了解一下即可
- 序列化当前对象,不需要克隆对象
- BeanUtil的什么方法的底层是通过反射实现的属性浅(前?)拷贝,在复制大量对象的场景应该有限选择克隆模式???
- 从效率上来说,clone一个对象不比new一个对象要快,他只是省略了给属性赋值的语句
- 当一个对象属性多且赋值很麻烦,并且就需要大量同一对象的时候,用原型模式比较方便
🤏随想
- 父类方法的访问修饰符为protected,子类的方位修饰符可以改成public
- 子类访问修饰符可以比父类访问修饰符宽松
- 当遇到多层的对象的对象时(对象作为成员属性),要在每一层类的clone方法中显式调用对象属性的clone方法(对象属性所对应的类要重写clone方法并实现Cloneable接口)