浅拷贝
在Java编程中,浅拷贝是指在复制对象时,只复制对象的基本数据类型的值和引用类型的地址,**不复制引用类型指向的对象本身。**浅拷贝可以用于一些简单的场景,例如对象的基本属性不包含其他对象的引用类型,或者不需要修改对象引用类型所指向的对象。
说白了就是,基本数据类型和对象(不包括对象里面的属性值,指包括字段)进行复制
以下是几个使用浅拷贝的场景:
- 原型模式:在创建一个新对象时,如果该对象和已有对象的属性相同,可以使用浅拷贝来复制已有对象的属性,而不必重新创建一个新对象。
- 缓存数据:当需要缓存某些数据时,可以使用浅拷贝来创建缓存对象。如果原始对象不再使用,可以直接将其赋值为null,而不必担心缓存对象的引用被同时置为null。
- 复制属性:当需要将一个对象的属性值复制到另一个对象时,可以使用浅拷贝。例如,将一个对象的属性值复制到一个DTO(数据传输对象)中,以传递给其他系统或服务。
- 方法一 :直接赋值
class Song {
String title;
String artist;
Song(String title, String artist) {
this.title = title;
this.artist = artist;
}
}
@Data
public class Playlist {
private Long id;
private String name;
private List<Song> songs = new ArrayList<>();
public Playlist() {
}
public void add(Song song){
songs.add(song);
}
public Playlist(Playlist sourcePlayList) {
this.id = sourcePlayList.getId();
this.name = sourcePlayList.getName();
this.songs = sourcePlayList.getSongs();
}
}
测试 :
public static void main(String[] args) {
Playlist playlist = new Playlist();
playlist.setId(1L);
playlist.setName("杰伦");
playlist.add(new Song("稻香","杰伦"));
playlist.add(new Song("迷迭香","杰伦"));
playlist.add(new Song("七里香","杰伦"));
// 浅拷贝后的最喜爱的专辑
Playlist favouriteList = new Playlist(playlist);
favouriteList.add(new Song("曹操","林俊杰"));
System.out.println(favouriteList);
}
- 方法二 :实现Cloneable接口
@Data
public class Playlist2 implements Serializable, Cloneable {
private Long id;
private String name;
private List<Song> songs = new ArrayList<>();
public Playlist2() {
}
public void add(Song song){
songs.add(song);
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Playlist2 playlist = new Playlist2();
playlist.setId(1L);
playlist.setName("杰伦");
playlist.add(new Song("稻香","杰伦"));
playlist.add(new Song("迷迭香","杰伦"));
playlist.add(new Song("七里香","杰伦"));
// 浅拷贝后的最喜爱的专辑
Playlist2 favouriteList = (Playlist2) playlist.clone();
System.out.println(favouriteList);
}
}
深拷贝
深拷贝的实现,通常有两个思路,一个是递归克隆,一个是使用序列化的手段,我们分别对以下两种方式进行讲解。
- 递归克隆
class Product implements Cloneable {
private String name;
private double price;
private int stock;
// 省略构造函数、getter和setter方法
@Override
public Product clone() {
try {
return (Product) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销规则
class PromotionRule implements Cloneable {
private String type;
private double discount;
private Product product;
// 省略构造函数、getter和setter方法
@Override
protected PromotionRule clone() {
try {
PromotionRule promotionRule = (PromotionRule) super.clone()
Product product = (Product)product.clone();
promotionRule.setProduct(product);
return promotionRule;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销活动
class PromotionEvent implements Cloneable {
private String name;
private Date startDate;
private Date endDate;
private List<PromotionRule> rules;
// 省略构造函数、getter和setter方法
// 在促销活动中的clone方法需要克隆里边所有的非基础数据类型
@Override
protected PromotionEvent clone() {
try {
PromotionEvent clonedEvent = (PromotionEvent) super.clone();
clonedEvent.startDate = (Date) startDate.clone();
clonedEvent.endDate = (Date) endDate.clone();
clonedEvent.rules = new ArrayList<>();
for (PromotionRule rule : rules) {
clonedEvent.rules.add(rule.clone());
}
return clonedEvent;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
原型模式应用 — CopyOnWrite
在JDK中,原型设计模式的应用并不非常广泛。然而,在需要快速创建具有相似属性的新对象时,原型设计模式提供
还有一个典型例子,CopyOnWriteArrayList:
public Object clone() {
try {
@SuppressWarnings("unchecked")
CopyOnWriteArrayList<E> clone =
(CopyOnWriteArrayList<E>) super.clone();
clone.resetLock();
// Unlike in readObject, here we cannot visibility-piggyback on the
// volatile write in setArray().
VarHandle.releaseFence();
return clone;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
CopyOnWriteArrayList中的add()
注意跟下面的set()做好区分,因为涉及到扩容,所以原型模式是用不了的,在set()方法里面是用的原型设计模式
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
原来 写时复制技术 的底层之一就是原型设计模式的应用案例
在我们对集合进行修改时,他通过克隆技术,对原数据进行了克隆,原始版本不受影响:
public E set(int index, E element) {
synchronized (lock) {
Object[] es = getArray();
E oldValue = elementAt(es, index);
if (oldValue != element) {
// 克隆
es = es.clone();
es[index] = element;
}
// Ensure volatile write semantics even when oldvalue == element
setArray(es);
return oldValue;
}
}