Java四種拷貝方式實戰(zhàn)總結(jié)(一文掃清所有拷貝問題)
前言
作為Java開發(fā)者,日常開發(fā)中經(jīng)常會遇到數(shù)據(jù)拷貝的需求。最近在面試中也有被問到一次大文件拷貝,抽空專門總結(jié)一下,從基礎(chǔ)概念到實戰(zhàn)技巧,配合流程圖,讓原理和流程一目了然~
一、淺拷貝VS深拷貝
一開始做spring項目時,就被淺拷貝坑過。當(dāng)時想復(fù)制一個對象,結(jié)果改了新對象的屬性,老對象也跟著變了。后來才知道,這就是淺拷貝的“鍋”。
淺拷貝就像拍證件照,照片上的人看著和你一模一樣,但本質(zhì)上還是兩張紙。Java里的淺拷貝只會復(fù)制對象的基本屬性,遇到引用類型(比如自定義類),就直接把地址抄過來。所以你改新對象里的引用屬性,老對象也會跟著遭殃。
深拷貝就靠譜多了,它會把對象里里外外都復(fù)制一遍,就像克隆人,新對象和老對象完全獨立。雖然麻煩點,但勝在安全,適合處理復(fù)雜對象。

舉個例子,有個Person類包含name和Address屬性:
class Address {
String city;
}
class Person {
String name;
Address address;
// 淺拷貝
Person shallowCopy() {
Person copy = new Person();
copy.name = this.name;
copy.address = this.address;
return copy;
}
// 深拷貝
Person deepCopy() {
Person copy = new Person();
copy.name = this.name;
copy.address = new Address();
copy.address.city = this.address.city;
return copy;
}
}
二、大文件拷貝:別讓你的程序“龜速運(yùn)行”
之前做文件上傳功能,用傳統(tǒng)IO方式拷貝大文件,結(jié)果服務(wù)器直接卡住。后來換成NIO,速度直接起飛!
傳統(tǒng)IO就像螞蟻搬家,一次搬一點,頻繁讀寫磁盤;NIO則像卡車運(yùn)輸,直接把數(shù)據(jù)從一個地方搬到另一個地方,速度快得多。

// 傳統(tǒng)IO方式,適合小文件
public static void copyFileByIO(File source, File dest) throws IOException {
try (InputStream is = new FileInputStream(source);
OutputStream os = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
}
}
// NIO方式,適合大文件
public static void copyFileByNIO(File source, File dest) throws IOException {
try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
FileChannel destChannel = new FileOutputStream(dest).getChannel()) {
destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
}
}
三、對象拷貝的進(jìn)階方案:使用造好的輪子
手動寫深拷貝代碼太麻煩?別慌,Java提供給我們有不少寫好的辦法。
1. 序列化與反序列化
就像把對象打包成快遞,寄出去再拆開。雖然速度慢點,但能保證完全獨立的拷貝。不過要注意,所有相關(guān)類都得實現(xiàn)Serializable接口。

public static <T extends Serializable> T deepCopyBySerialization(T obj) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais)) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
2. JSON序列化
用Jackson或Gson把對象轉(zhuǎn)成JSON字符串,再轉(zhuǎn)回來,簡單粗暴。不過這種方式要求對象的屬性都能被JSON正確解析。

import com.fasterxml.jackson.databind.ObjectMapper;
public static <T> T deepCopyByJSON(T obj, Class<T> clazz) {
try {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(obj);
return mapper.readValue(json, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
四、數(shù)組和集合的拷貝
數(shù)組的clone()方法看似簡單,實則暗藏玄機(jī):基本類型數(shù)組用它是深拷貝,對象數(shù)組就是淺拷貝。

int[] intArray = {1, 2, 3};
int[] copiedIntArray = intArray.clone(); // 深拷貝
Person[] personArray = new Person[2];
Person[] copiedPersonArray = personArray.clone(); // 淺拷貝
集合類的構(gòu)造函數(shù)也是淺拷貝,比如new ArrayList<>(originalList)。想深拷貝集合,得手動遍歷每個元素。

List<Person> originalList = new ArrayList<>();
List<Person> deepCopiedList = originalList.stream()
.map(Person::deepCopy)
.collect(Collectors.toList());
五、第三方庫:站在巨人的肩膀上
不想自己造輪子?Apache Commons Lang和Dozer能幫你大忙。前者提供了便捷的序列化拷貝工具,后者擅長對象屬性映射,用起來超方便。
<!-- Apache Commons Lang依賴 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
// 使用示例
Person copiedPerson = SerializationUtils.clone(originalPerson);
六、總結(jié):看不同場景選擇合適的拷貝方式
- 簡單對象用淺拷貝,復(fù)雜對象用深拷貝
- 大文件優(yōu)先用NIO,小文件用傳統(tǒng)IO
- 能偷懶就偷懶,善用第三方庫
- 多寫測試用例,別讓拷貝“埋雷”
到此這篇關(guān)于Java四種拷貝方式實戰(zhàn)文章就介紹到這了,更多相關(guān)Java四種拷貝方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中生成任意之間數(shù)的隨機(jī)數(shù)詳解
這篇文章主要介紹了java中生成任意之間數(shù)的隨機(jī)數(shù)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09
IntelliJ IDEA 2021.1 EAP 4 發(fā)布:字體粗細(xì)可調(diào)整Git commit template 支持
這篇文章主要介紹了IntelliJ IDEA 2021.1 EAP 4 發(fā)布:字體粗細(xì)可調(diào)整,Git commit template 支持,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
redis.clients.jedis.exceptions.JedisAskDataException異常解決
redis.clients.jedis.exceptions.JedisAskDataExceptio異常是在使用Jedis客戶端與Redis集群交互時遇到的一種重定向異常,本文就來介紹一下解決方法,感興趣的可以了解一下2024-05-05
JavaWeb HttpServletResponse對象及常用方法
這篇文章主要介紹了JavaWeb-HttpServletResponse對象,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
IDEA創(chuàng)建SpringBoot父子Module項目的實現(xiàn)
本文主要介紹了IDEA創(chuàng)建SpringBoot父子Module項目的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05

