對象的深度複製和淺複製java
(深度拷貝和淺拷貝)程序員
做者:Jesai數據庫
時間:2018年2月11日 21:46:22編程
咱們在實際的開發項目裏面爲了使得開發更加的便捷和方便,總會不經意的使用一些第三方的持久化框架(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),好比C#裏面的ADO.NET Entity Framework、Nhibernate以及java裏面的MyBaits和Hibernate等等,有時候,咱們須要複製一個持久化對象,而後再持久化到數據庫裏面去,每每有同窗是這麼操做的,下面我以java的Hibernate爲例子。數組
1 public Car getCarById(Long id) { 2 Criteria criteria = getSession().createCriteria(Car.class); 3 criteria.add(Restrictions.eq("id", id)); 4 return (Car)criteria.uniqueResult(); 5 }
經過上面代碼咱們等到一個Car的持久化對象。而後我如今要新增一輛如出一轍的車,除了車牌號不同和ID主鍵不同。咱們是怎麼作的呢,首先,剛寫編程不久的同窗就想到了下面的方法app
1 public void Save(Car car) { 2 Car carNew=new Car(); 3 carNew.Id=」1」; 4 carNew.Color= car. Color; 5 carNew.Price= car. Price; 6 carNew.No=」33333」; 7 …… 8 getSession().Save(car); 9 }
這是新手最喜歡用的方式,可是對於一個老程序員來講,他們可不想寫那麼長長的一段代碼,老程序員的思想老是寫最少的代碼,幹更多的事情。因而,有「聰明」的同窗是這麼作的框架
1 public void Save(Car car) { 2 Car carNew=new Car(); 3 carNew =car; 4 carNew.Id=」3」; 5 carNew.No=」33333」; 6 getSession().Save(car); 7 }
的確是簡潔了不少呢,當這個同窗很興奮的去執行測試的時候就會發現Hibernate報錯,函數
咱們總會在日常的工做裏遇到對象克隆的問題。也許有不少同窗會被這個問題所困擾。這究竟是怎麼回事呢?測試
這就須要用值引用和地址引用的知識來解釋這個現象了,咱們先來看看什麼是值引用和地址引用,this
引用傳遞的是對象的地址,值傳遞的是變量的值
使用引用傳遞,被調用函數使用的是調用函數傳入的對象自己,也就是說在被調用函數中對對象進行修改將直接致使外部對象的值被修改。
而值傳遞,傳遞進去的是變量的副本(即拷貝),此時在被調用函數中對形參的任何修改都不會改變外部變量的值。
值傳遞是傳遞數據:如基本數據類型都是值傳遞
引用傳遞是把形參和實參的指針指向了堆中的同一對象,對象的引用和數組的引用。
如
1 Int a=10; 2 3 Int b=a; 4 5 a=20;
變量和對象的實例化的時候都會在計算機的內存裏面申請一個內存,值引用是a和b各自申請一個區域存放數據。當a再次改變值的時候,b並不會隨着a的改變而改變。
1 Student s=new Student(); 2 3 s.Name=」Tom」; 4 5 Student s1=new Student(); 6 7 s1=s; 8 9 s.Name=「Jon」;
對象就是地址引用,在這裏,當後面s的Name發生改變的時候,s1也是跟隨着改變的。
值引用
地址引用
在上面圖示咱們能夠看出,值引用是各自分配內存的,本質上不是同一個東西。就比如一對雙胞胎,長得很像,可是根本是兩我的。而地址引用就比如李明有一個小名叫小明。可是小明和李明都是同一我的。
什麼是值傳遞,地址傳遞和引用傳遞?首先,看如下三段代碼。
1 void Test(int x, int y) 2 { 3 int tmp=x; 4 x=y; 5 y=tmp; 6 print(「x=%d, y=%d\n」, x, y); 7 } 8 9 void main() 10 { 11 12 int a=4,b=5; 13 Test(a,b) ; 14 printf(「a=%d, b=%d\n」, a, b); 15 }
輸出結果
x=4, y=5
a=5, b=4
1 void Test(int *px, int *py) 2 { 3 int tmp=*px; 4 *px=*py; 5 *py=tmp; 6 print(「*px=%d, *py=%d\n」, *px, *py); 7 } 8 9 void main() 10 { 11 12 int a=4; 13 int b=5; 14 swap2(&a,&b); 15 Print(「a=%d, b=%d\n」, a, b); 16 }
此次輸出結果
*px=5, *py=5
a=5, b=4
1 void Test(int &x, int &y) 2 { 3 int tmp=x; 4 x=y; 5 y=tmp; 6 print(「x=%d, y=%d\n」, x, y); 7 } 8 9 void main() 10 { 11 12 int a=4; 13 int b=5; 14 swap3(a,b); 15 Print(「a=%d, b=%d\n」, a, b); 16 }
此次輸出結果是
---
x=5, y=4
a=5, b=4
上面的分別是值傳遞,地址傳遞,和引用傳遞。
先看值傳遞。是將x,y進行對調。須要注意的是,對形參的操做不會影響到a,b。當a,b把值賦給x,y以後,對x,y發生改變,不會影響到a,b自己。
再看地址傳遞。a的地址代入到了px,b的地址代入到了py。這樣一來,對*px, *py的操做就是a,b自己的操做。因此a,b的值被對調了。
引用傳遞。定義的x,y前面有&取地址符,a,b分別代替了x,y,即x,y分別引用了a,b變量。所以,函數裏的操做,其實是對實參a,b自己的操做,其值發生了對調。
咱們怎麼判斷兩個對象是相等(深複製)的呢?能夠使用object.ReferenceEquals(s1, s2)。
好了,那麼下面咱們回來探討一下對象的深度克隆和淺克隆的幾種方法:
寫一個Sudent類:
1 using System; 2 3 using System.Collections.Generic; 4 5 using System.Linq; 6 7 using System.Text; 8 9 using System.Threading.Tasks; 10 11 12 13 namespace Model.test 14 15 { 16 17 [Serializable] 18 19 public class Student 20 21 { 22 23 public Student() { } 24 25 private string _name; 26 27 private string _age; 28 29 private string _sex; 30 31 private string _address; 32 33 private string _tel; 34 35 public Student(string name, string age, string sex, string address, string tel) 36 37 { 38 39 this.address = address; 40 41 this.age = age; 42 43 this.name = name; 44 45 this.sex = sex; 46 47 this.tel = tel; 48 49 } 50 51 public string name { get; set; } 52 53 public string age { get; set; } 54 55 public string sex { get; set; } 56 57 public string address { get; set; } 58 59 public string email { get; set; } 60 61 public string tel { get; set; } 62 63 } 64 65 }
咱們先來看看對象直接賦值:
1 static void Main(string[] args) 2 3 { 5 Student s1 = new Student(); 6 7 s1.address = "北京"; 8 9 s1.sex = "男"; 10 11 s1.tel = "18825196284"; 12 13 s1.name = "張峯"; 14 15 s1.email = "777663548@QQ.COM"; 16 17 s1.age = "25"; 18 19 Student s2 = new Student(); 20 21 s2 = s1; 25 //更改s2的年齡 26 27 s2.age = "44"; 28 33 Console.WriteLine("s1的年齡:" + s1.age + " S2 HashCode:" + s2.GetHashCode() + " S1 HashCode:" + s1.GetHashCode() + " 直接複製 s2 = s1 的ReferenceEquals結果:" + object.ReferenceEquals(s1, s2)); 34 35 Console.ReadLine(); 36 37 }
結果說明這兩個對象是地址引用,即淺複製。
再來看下面的狀況:
1 static void Main(string[] args) 2 3 { 4 5 Student s1 = new Student(); 6 7 s1.address = "北京"; 8 9 s1.sex = "男"; 10 11 s1.tel = "18825196284"; 12 13 s1.name = "張峯"; 14 15 s1.email = "777663548@QQ.COM"; 16 17 s1.age = "25"; 27 Student s2 = new Student(); 28 29 s2.address = s1.address; 30 31 s2.sex = s1.sex; 32 33 s2.tel = s1.tel; 34 35 s2.name = s1.name; 36 37 s2.email = s1.email; 38 39 s2.age = s1.age; 40 41 //更改s2的年齡 42 43 s2.age = "55"; 47 Console.WriteLine("s1的年齡:" + s1.age + " S2 HashCode:" + s2.GetHashCode() + " S1 HashCode:" + s1.GetHashCode() + " 直接複製 s2 = s1 的ReferenceEquals結果:" + object.ReferenceEquals(s1, s2)); 48 49 Console.ReadLine(); 50 51 }
能夠看出來屬性一個一個賦值是深度克隆,可是這樣代碼量就大了。那麼有什麼辦法寫不多的代碼嗎?
第一種方法-對象映射:
1 public static object CloneObject(object o) 2 3 { 4 5 Type t = o.GetType(); 6 7 PropertyInfo[] properties = t.GetProperties(); 8 9 Object p = t.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, o, null); 10 11 foreach (PropertyInfo pi in properties) 12 13 { 14 15 if (pi.CanWrite) 16 17 { 18 object value = pi.GetValue(o, null); 19 20 pi.SetValue(p, value, null); 21 22 } 23 24 } 25 26 return p; 27 28 } 29 30 static void Main(string[] args) 31 32 { 33 34 Student s1 = new Student(); 35 36 s1.address = "北京"; 37 38 s1.sex = "男"; 39 40 s1.tel = "18825196284"; 41 42 s1.name = "張峯"; 43 44 s1.email = "777663548@QQ.COM"; 45 46 s1.age = "25"; 47 48 Student s2 = new Student(); 49 50 s2 = (Student)CloneObject(s1); 51 52 //更改S2的年齡 53 54 s2.age = "88"; 55 Console.WriteLine("s1的年齡:" + s1.age + " S2 HashCode:" + s2.GetHashCode() + " S1 HashCode:" + s1.GetHashCode() + " 直接複製 s2 = s1 的ReferenceEquals結果:" + object.ReferenceEquals(s1, s2)); 56 57 Console.ReadLine(); 58 59 }
第二種方法(二進制流序列化反序列化):
1 static void Main(string[] args) 2 3 { 4 5 Student s1 = new Student(); 6 7 s1.address = "北京"; 8 9 s1.sex = "男"; 10 11 s1.tel = "18825196284"; 12 13 s1.name = "張峯"; 14 15 s1.email = "777663548@QQ.COM"; 16 17 s1.age = "25"; 18 19 Student s2 = new Student(); 20 21 BinaryFormatter inputFormatter = new BinaryFormatter(); 22 23 MemoryStream inputStream; 24 25 using (inputStream = new MemoryStream()) 26 27 { 28 29 inputFormatter.Serialize(inputStream, s1); 30 } 31 32 //將二進制流反序列化爲對象 33 34 using (MemoryStream outputStream = new MemoryStream(inputStream.ToArray())) 35 36 { 37 BinaryFormatter outputFormatter = new BinaryFormatter(); 38 39 s2 = (Student)outputFormatter.Deserialize(outputStream); 40 41 } 42 43 //更改S2的年齡 44 45 s2.age = "88"; 46 47 Console.WriteLine("s1的年齡:" + s1.age + " S2 HashCode:" + s2.GetHashCode() + " S1 HashCode:" + s1.GetHashCode() + " 直接複製 s2 = s1 的ReferenceEquals結果:" + object.ReferenceEquals(s1, s2)); 48 49 Console.ReadLine(); 50 51 }
第三種(AutoMapper):
1 using System; 2 3 using System.Collections; 4 5 using System.Collections.Generic; 6 7 using System.Data; 8 9 using System.Linq; 10 11 using System.Text; 12 13 using System.Threading.Tasks; 14 15 16 17 namespace AutoMapper.AutoMapperHelpers 18 19 { 20 21 /// <summary> 22 23 /// AutoMapper擴展幫助類 24 25 /// </summary> 26 27 public static class AutoMapperHelper 28 29 { 30 31 /// <summary> 32 33 /// 類型映射 34 35 /// </summary> 36 37 public static T MapTo<T>(this object obj) 38 39 { 40 41 if (obj == null) return default(T); 42 43 Mapper.CreateMap(obj.GetType(), typeof(T)); 44 45 return Mapper.Map<T>(obj); 46 47 } 48 49 /// <summary> 50 51 /// 集合列表類型映射 52 53 /// </summary> 54 55 public static List<TDestination> MapToList<TDestination>(this IEnumerable source) 56 57 { 58 59 foreach (var first in source) 60 61 { 62 63 var type = first.GetType(); 64 65 Mapper.CreateMap(type, typeof(TDestination)); 66 67 break; 68 69 } 70 71 return Mapper.Map<List<TDestination>>(source); 72 73 } 74 75 /// <summary> 76 77 /// 集合列表類型映射 78 79 /// </summary> 80 81 public static List<TDestination> MapToList<TSource, TDestination>(this IEnumerable<TSource> source) 82 83 { 84 85 //IEnumerable<T> 類型須要建立元素的映射 86 87 Mapper.CreateMap<TSource, TDestination>(); 88 89 return Mapper.Map<List<TDestination>>(source); 90 91 } 92 93 /// <summary> 94 95 /// 類型映射 96 97 /// </summary> 98 99 public static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination) 100 101 where TSource : class 102 103 where TDestination : class 104 105 { 106 107 if (source == null) return destination; 108 109 Mapper.CreateMap<TSource, TDestination>(); 110 111 return Mapper.Map(source, destination); 112 113 } 114 115 /// <summary> 116 117 /// DataReader映射 118 119 /// </summary> 120 121 public static IEnumerable<T> DataReaderMapTo<T>(this IDataReader reader) 122 123 { 124 125 Mapper.Reset(); 126 127 Mapper.CreateMap<IDataReader, IEnumerable<T>>(); 128 129 return Mapper.Map<IDataReader, IEnumerable<T>>(reader); 130 131 } 132 133 } 134 135 }
1 static void Main(string[] args) 2 3 { 4 5 Student s1 = new Student(); 6 7 s1.address = "北京"; 8 9 s1.sex = "男"; 10 11 s1.tel = "18825196284"; 12 13 s1.name = "張峯"; 14 15 s1.email = "777663548@QQ.COM"; 16 17 s1.age = "25"; 18 19 Student s2 = new Student(); 20 21 s2 = AutoMapperHelper.MapTo<Student>(s1); 22 23 //更改S2的年齡 24 s2.age = "88"; 25 26 Console.WriteLine("s1的年齡:" + s1.age + " S2 HashCode:" + s2.GetHashCode() + " S1 HashCode:" + s1.GetHashCode() + " 直接複製 s2 = s1 的ReferenceEquals結果:" + object.ReferenceEquals(s1, s2)); 27 28 Console.ReadLine(); 29 30 }
第四種JSON序列化和反序列化:
1 static void Main(string[] args) 2 3 { 4 5 Student s1 = new Student(); 6 7 s1.address = "北京"; 8 9 s1.sex = "男"; 10 11 s1.tel = "18825196284"; 12 13 s1.name = "張峯"; 14 15 s1.email = "777663548@QQ.COM"; 16 17 s1.age = "25"; 18 19 Student s2 = Newtonsoft.Json.JsonConvert.DeserializeObject<Student>(Newtonsoft.Json.JsonConvert.SerializeObject(s1)); 20 21 //更改S2的年齡 22 23 s2.age = "88"; 24 25 Console.WriteLine("s1的年齡:" + s1.age + " S2 HashCode:" + s2.GetHashCode() + " S1 HashCode:" + s1.GetHashCode() + " 直接複製 s2 = s1 的ReferenceEquals結果:" + object.ReferenceEquals(s1, s2)); 26 27 Console.ReadLine(); 28 29 }