이 글은 주로 자바 객체 복사에 대한 자세한 설명과 예시를 소개하고 있습니다. 필요한 친구들은
자바 객체 복사에 대한 자세한 설명과 예시
를 참고하세요. Java 할당은 객체 참조를 복사하는 것입니다. 객체의 복사본을 얻으려는 경우 할당 작업을 사용하면 목표를 달성할 수 없습니다.
@Test public void testassign(){ Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); Person p2=p1; System.out.println(p1==p2);//true }
객체의 새 복사본을 생성하면 다음과 같습니다. 초기 상태는 정확히 동일하지만 나중에 서로 영향을 주지 않고 각 상태를 변경할 수 있으므로 기본 clone() 메서드와 같은 Java의 객체 복사본을 사용해야 합니다.
객체 복제 방법
Object 객체에는 객체의 각 속성을 복사하는 clone() 메서드가 있지만 가시 범위는 보호됩니다. , 따라서 엔터티 클래스에 대한 복제를 사용하기 위한 전제 조건은 다음과 같습니다.
① 마커 인터페이스이며 자체 메서드가 없는 Cloneable 인터페이스를 구현합니다.
② clone() 메소드를 재정의하여 공개 가시성을 높입니다.
@Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Test public void testShallowCopy() throws Exception{ Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); Person p2=(Person) p1.clone(); System.out.println(p1==p2);//false p2.setName("Jacky"); System.out.println("p1="+p1);//p1=Person [name=Peter, age=31] System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31] }
이 테스트 케이스에는 기본 유형의 멤버가 두 개만 있으며 테스트는 목적을 달성합니다.
상황이 그리 간단하지 않은 것 같습니다. Person에 Address 클래스의 멤버를 추가합니다.
@Data public class Address { private String type; private String value; }
다시 테스트하면 문제가 발생합니다.
@Test public void testShallowCopy() throws Exception{ Address address=new Address(); address.setType("Home"); address.setValue("北京"); Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); p1.setAddress(address); Person p2=(Person) p1.clone(); System.out.println(p1==p2);//false p2.getAddress().setType("Office"); System.out.println("p1="+p1); System.out.println("p2="+p2); }
출력 보기:
false p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
에 문제가 발생했습니다. p2의 주소 유형만 수정되었으며 두 주소 유형 모두 Office로 변경되었습니다.
얕은 복사와 깊은 복사
이전의 예는 얕은 복사와 깊은 복사의 일반적인 사용 사례입니다.
얕은 복사: 복사된 개체의 모든 값 속성은 원본 개체의 값과 동일한 값을 포함하며 모든 개체 참조 속성은 여전히 원본 개체를 가리킵니다.
깊은 복사: 얕은 복사를 기반으로 다른 개체를 참조하는 모든 변수도 복제되어 복사된 새 개체를 가리킵니다.
즉, 기본 clone() 메소드 구현 메커니즘은 여전히 할당입니다.
복사된 속성이 기본 유형인 경우 현재 클래스의 복제 가능한 메커니즘만 구현하면 됩니다. 이는 얕은 복사본입니다.
복사된 객체의 속성에 다른 항목 클래스 객체에 대한 참조가 포함되어 있는 경우 이러한 항목 클래스 객체는 복제 가능한 인터페이스를 구현하고 clone() 메서드를 재정의해야 합니다.
@Data public class Address implements Cloneable { private String type; private String value; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
이것만으로는 충분하지 않습니다. Person의 clone()은 해당 참조 멤버를 명시적으로 복제해야 합니다.
@Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { Object obj=super.clone(); Address a=((Person)obj).getAddress(); ((Person)obj).setAddress((Address) a.clone()); return obj; } }
이전 테스트 케이스 다시 실행:
false p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
clone deep copy 요약
① 네이티브가 아닌 멤버가 있는 경우 사용자 정의 개체 멤버인 경우 다음이 필요합니다.
이 멤버는 Cloneable 인터페이스를 구현하고 clone() 메서드를 재정의하여 공개 가시성으로 승격시키는 것을 잊지 마세요.
동시에 복사된 클래스의 clone() 메소드를 수정하고 멤버 복제 로직을 추가합니다.
② 복사된 객체가 Object를 직접 상속하지 않는 경우 그 사이에 다른 상속 수준이 있습니다. 각 슈퍼 클래스는 Cloneable 인터페이스를 구현하고 clone() 메서드를 재정의해야 합니다.
객체 멤버와 달리 상속 관계의 클론은 복사된 클래스의 clone()에 의한 중복 작업이 필요하지 않습니다.
한마디로 완전한 딥 카피를 구현한다면, 복사된 객체의 상속 체인과 참조 체인에 있는 모든 객체는 복제 메커니즘을 구현해야 합니다.
앞의 예는 괜찮지만, N개 객체 멤버와 M레벨 상속 관계가 있다면 매우 번거로울 것입니다.
신뢰할 수 있고 간단한 방법을 찾고 있다면 직렬화를 사용하는 것이 좋습니다. 1. 복사된 객체의 상속 체인과 참조 체인의 각 객체는 java.io.Serialized 인터페이스를 구현합니다. 이는 비교적 간단하며 구현하는 데 메소드가 필요하지 않습니다. serialVersionID 요구 사항은 필수가 아니며 전체 복사에 적합합니다.
2. 자신만의 deepClone 메서드를 구현하고 이를 스트림에 쓴 다음 읽어보세요. 일반적으로 다음과 같이 알려져 있습니다. 동결-해동.
@Data public class Person implements Serializable { private String name; private Integer age; private Address address; public Person deepClone() { Person p2=null; Person p1=this; PipedOutputStream out=new PipedOutputStream(); PipedInputStream in=new PipedInputStream(); try { in.connect(out); } catch (IOException e) { e.printStackTrace(); } try(ObjectOutputStream bo=new ObjectOutputStream(out); ObjectInputStream bi=new ObjectInputStream(in);) { bo.writeObject(p1); p2=(Person) bi.readObject(); } catch (Exception e) { e.printStackTrace(); } return p2; } }프로토타입 팩토리 클래스
테스트를 용이하게 하고 공간을 절약하기 위해 팩토리 클래스를 캡슐화합니다.
공정성을 위해 일부 도구 라이브러리에서는 캐싱 메커니즘을 사용하지 말고 프로토타입 팩토리를 사용하세요. public class PersonFactory{
public static Person newPrototypeInstance(){
Address address = new Address();
address.setType("Home");
address.setValue("北京");
Person p1 = new Person();
p1.setAddress(address);
p1.setAge(31);
p1.setName("Peter");
return p1;
}
}
Dozer는 Bean 처리 라이브러리입니다.
maven 종속성<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.5.1</version>
</dependency>
테스트 사례:
@Data public class Person { private String name; private Integer age; private Address address; @Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); Person p2 = mapper.map(p1, Person.class); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); } } @Data public class Address { private String type; private String value; }
출력:
p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
참고: DozerBeanMapper의 경우 10,000번의 테스트에서 dozer에 심각한 문제가 있습니다. for 루프에서 객체가 생성되고 효율성(dozer:7358)이 거의 10배 감소합니다. DozerBeanMapper는 스레드로부터 안전하므로 매번 새 인스턴스를 생성해서는 안 됩니다. 내장된 싱글톤 팩토리 DozerBeanMapperSingletonWrapper를 사용하여 매퍼를 생성하거나 이를 Spring에 통합할 수 있습니다.
더 폭력적이라면 People 클래스를 만드세요:
@Data public class People { private String name; private String age;//这里已经不是Integer了 private Address address; @Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); People p2 = mapper.map(p1, People.class); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); } }
속성 이름만 같으면 하세요~
계속 파괴하세요:
@Data public class People { private String name; private String age; private Map<String,String> address;//�� @Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); People p2 = mapper.map(p1, People.class); p2.getAddress().put("type", "Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); } }Commons-BeanUtils를 사용하여 객체 복사
maven 종속성
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
테스트 사례: @Data
public class Person {
private String name;
private String age;
private Address address;
@Test
public void testCommonsBeanUtils(){
Person p1=PersonFactory.newPrototypeInstance();
try {
Person p2=(Person) BeanUtils.cloneBean(p1);
System.out.println("p1=" + p1);
p2.getAddress().setType("Office");
System.out.println("p2=" + p2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Maven 종속성:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
테스트 케이스: @Test
public void testCglib(){
Person p1=PersonFactory.newPrototypeInstance();
BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
Person p2=new Person();
beanCopier.copy(p1, p2,null);
p2.getAddress().setType("Office");
System.out.println("p1=" + p1);
System.out.println("p2=" + p2);
}
결과는 충격적입니다. cglib은 정말 훌륭하고 실제로는 얕은 복사본입니다. 그러나 cglib는 확장 기능을 제공합니다.
@Test public void testCglib(){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true); Person p2=new Person(); beanCopier.copy(p1, p2, new Converter(){ @Override public Object convert(Object value, Class target, Object context) { if(target.isSynthetic()){ BeanCopier.create(target, target, true).copy(value, value, this); } return value; } }); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); }
Orika复制对象
orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。
maven依赖:
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.0</version> </dependency> </dependencies>
测试用例:
@Test public void testOrika() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Person.class, Person.class) .byDefault() .register(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); MapperFacade mapper = mapperFactory.getMapperFacade(); Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); System.out.println("p1=" + p1); p2.getAddress().setType("Office"); System.out.println("p2=" + p2); }
Spring BeanUtils复制对象
给Spring个面子,貌似它不支持深拷贝。
Person p1=PersonFactory.newPrototypeInstance(); Person p2 = new Person(); Person p2 = (Person) BeanUtils.cloneBean(p1); //BeanUtils.copyProperties(p2, p1);//这个更没戏
深拷贝性能对比
@Test public void testBatchDozer(){ Long start=System.currentTimeMillis(); Mapper mapper = new DozerBeanMapper(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); } System.out.println("dozer:"+(System.currentTimeMillis()-start)); //dozer:721 } @Test public void testBatchBeanUtils(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); try { Person p2=(Person) BeanUtils.cloneBean(p1); } catch (Exception e) { e.printStackTrace(); } } System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start)); //commons-beanutils:229 } @Test public void testBatchCglib(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true); Person p2=new Person(); beanCopier.copy(p1, p2, new Converter(){ @Override public Object convert(Object value, Class target, Object context) { if(target.isSynthetic()){ BeanCopier.create(target, target, true).copy(value, value, this); } return value; } }); } System.out.println("cglib:"+(System.currentTimeMillis()-start)); //cglib:133 } @Test public void testBatchSerial(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); Person p2=p1.deepClone(); } System.out.println("serializable:"+(System.currentTimeMillis()-start)); //serializable:687 } @Test public void testBatchOrika() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Person.class, Person.class) .field("name", "name") .byDefault() .register(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); MapperFacade mapper = mapperFactory.getMapperFacade(); Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); } System.out.println("orika:"+(System.currentTimeMillis()-start)); //orika:83 } @Test public void testBatchClone(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); try { Person p2=(Person) p1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } System.out.println("clone:"+(System.currentTimeMillis()-start)); //clone:8 }
(10k)性能比较:
//dozer:721 //commons-beanutils:229 //cglib:133 //serializable:687 //orika:83 //clone:8
深拷贝总结
原生的clone效率无疑是最高的,用脚趾头都能想到。
偶尔用一次,用哪个都问题都不大。
一般性能要求稍高的应用场景,cglib和orika完全可以接受。
另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。
위 내용은 Java 객체 복사의 자세한 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!