Java 객체 복사의 자세한 예
이 글은 주로 자바 객체 복사에 대한 자세한 설명과 예시를 소개하고 있습니다. 필요한 친구들은
자바 객체 복사에 대한 자세한 설명과 예시
를 참고하세요. 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레벨 상속 관계가 있다면 매우 번거로울 것입니다. <… 강제로 clone() 메서드를 재정의해야 하는 경우도 있습니다. 따라서 코딩 과정에서 링크 중 하나를 간과하기 쉽고 이로 인해 복잡한 프로젝트의 문제를 해결하기가 어렵습니다.
신뢰할 수 있고 간단한 방법을 찾고 있다면 직렬화를 사용하는 것이 좋습니다. 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); } }
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);
}
@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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Java의 난수 생성기 안내. 여기서는 예제를 통해 Java의 함수와 예제를 통해 두 가지 다른 생성기에 대해 설명합니다.

Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

자바의 암스트롱 번호 안내 여기에서는 일부 코드와 함께 Java의 Armstrong 번호에 대한 소개를 논의합니다.

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다
