Java 언어의 장점 중 하나는 포인터의 개념을 취소한다는 것입니다. 그러나 많은 프로그래머가 종종 프로그래밍의 객체와 참조의 차이를 무시하게됩니다. 이 기사는이 개념을 명확히하려고 노력할 것입니다. Java는 간단한 할당을 통해 객체 복사 문제를 해결할 수 없기 때문에 개발 과정에서 종종 복제 () 방법을 사용하여 객체를 복사해야합니다. 이 기사를 통해 Shadow Clone과 Deep Clone이 무엇인지 이해하고 차이점, 장점 및 단점을 이해할 수 있습니다.
이 제목을 볼 때 약간 혼란 스럽습니까? Java 언어는 포인터가 종종 편리하고 코드 불안의 근본 원인이기 때문에 포인터가 취소되었다고 분명히 말하고 프로그램을 매우 복잡하고 이해하기 어렵습니다. 포인터 남용에 의해 작성된 코드는 이미 악명 높은 "goto"진술을 사용하는 것 이상입니다. Java가 포인터의 개념을 포기하는 것은 절대적으로 현명합니다. 그러나 이것은 Java 언어에 명확한 포인터 정의가 없다는 것입니다. 본질적으로, 모든 새로운 진술은 포인터 참조를 반환합니다. 그러나 대부분의 경우 Java는이 "포인터"를 작동하는 방법에 대해 걱정할 필요가 없습니다. C ++에서 포인터를 작동 할 때처럼 두려워하지는 않습니다. 더 관심을 가져야 할 유일한 것은 객체를 기능으로 전달할 때입니다.
패키지 com.zoer.src; 공개 클래스 objref {obj aobj = new obj (); int aint = 11; public void changeobj (obj inobj) {inobj.str = "변경된 값"; } public void changepri (int inint) {inint = 22; } public static void main (String [] args) {objref oref = new objref (); System.out.println ( "Call ChangeObj () 이전 :" + oref.aobj); OREF.CHANGEOBJ (OREF.AOBJ); System.out.println ( "Call ChangeObj () 이후의 방법 :" + OREF.AOBJ); System.out.println ( "=========================================================================================================== =================================================================================================================================================== =================================================================================================================================================== =================================================================================================================================================== Call ChangePri () 메소드 : " + oref.aint); }} 패키지 com.zoer.src; 공개 클래스 obj {String str = "init value"; public String toString () {return str; }} 이 코드의 주요 부분은 매우 유사한 두 가지 방법 인 ChangeObj ()와 ChangePri ()를 호출합니다. 유일한 차이점은 객체를 입력 매개 변수로 취하고 다른 하나는 기본 유형 int를 Java의 입력 매개 변수로 취한다는 것입니다. 입력 매개 변수는 두 기능 본문 내에서 변경됩니다. 겉보기에는 동일한 방법이지만 프로그램의 출력 결과는 다릅니다. changeObj () 메소드는 입력 매개 변수를 실제로 변경하는 반면 changepri () 메소드는 입력 매개 변수를 변경하지 않습니다.
이 예에서 Java는 객체와 기본 데이터 유형을 다르게 처리합니다. C와 마찬가지로, Java의 기본 데이터 유형 (예 : int, char, double 등)이 입력 매개 변수로 기능 본문에 전달되면 전달 된 매개 변수는 기능 본문 내부의 로컬 변수가됩니다. 이 로컬 변수는 입력 매개 변수의 사본입니다. 기능 본문 내부의 모든 작업은이 사본의 작업입니다. 함수 실행이 완료되면 로컬 변수는 미션을 완료하며 변수에 입력 매개 변수에 영향을 미치지 않습니다. 이 매개 변수 전달 방법을 "값 통과"라고합니다. Java에서 객체를 항목 매개 변수로 사용하는 패스는 "참조 패스"이며, 이는 객체의 "참조"만 전달됨을 의미합니다. 이 "참조"의 개념은 C 언어의 포인터 참조와 동일합니다. 입력 변수가 함수 본문 내부에서 변경되면 기본적으로 객체에서 직접 작동합니다.
함수 값을 전달할 때 "참조 통과"를 제외하고 "="를 사용하여 객체 변수에 값을 할당 할 때 "참조 패스". 변수에 다른 별칭을 제공하는 것과 비슷합니다. 두 이름 모두 메모리에서 동일한 객체를 가리 킵니다.
실제 프로그래밍에서 우리는 종종이 상황에 직면합니다. 특정 순간에 유효한 값을 포함하는 객체 A가 있습니다. 이때, 새로운 객체 B는 A와 정확히 동일하게 필요할 수 있으며 B 로의 변경 사항은 A의 값에 영향을 미치지 않습니다. 즉, A와 B는 두 개의 독립적 인 객체이지만 B의 초기 값은 A 객체에 의해 결정됩니다. Java 언어로 간단한 할당 진술을 사용하면이 요구 사항을 충족 할 수 없습니다. 이러한 요구를 충족시키는 방법에는 여러 가지가 있지만 Clone () 방법을 구현하는 가장 간단하고 효율적인 방법이 그 중 하나입니다.
모든 Java 클래스는 기본적으로 java.lang.object 클래스를 상속하고 Java.lang.object 클래스에는 method clone ()가 있습니다. JDK API 설명서는이 메소드가 객체 객체의 사본을 반환 할 것이라고 설명합니다. 설명 할 두 가지 점이 있습니다. 첫째, 사본 객체는 참조가 아닌 새 객체를 반환합니다. 둘째, 객체를 복사하는 것과 새 객체를 새 연산자와 함께 반환하는 것의 차이점은이 사본에 이미 객체의 초기 정보가 아닌 원래 객체에 대한 정보가 포함되어 있다는 것입니다.
클론 () 메소드를 적용하는 방법?
Clone () 코드에 대한 매우 일반적인 호출은 다음과 같습니다.
공개 클래스 클로네 클래스는 복제 가능한 {public int aint; 공개 물체 클론 () {Cloneclass o = null; try {o = (cloneclass) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} 주목할 가치가있는 세 가지 점이 있습니다. 먼저, 클론 기능을 구현하려는 클로네 클래스 클래스는 클로닝 가능한 인터페이스를 구현합니다. 이 인터페이스는 java.lang 패키지에 속합니다. java.lang 패키지가 기본 클래스로 가져 왔으므로 java.lang.clonable로 작성할 필요가 없습니다. 주목할만한 또 다른 것은 Clone () 메소드가 과부하된다는 것입니다. 마지막으로, super.clone ()은 Clone () 메소드에서 호출되며, 이는 클론 클래스의 상속 구조가 어떤 모습이든 super.clone ()가 java.lang.object 클래스의 클론 () 메소드를 직접 또는 간접적으로 호출하는 모습을 의미합니다. 이 점들을 아래에서 자세히 설명해 봅시다.
세 번째 요점이 가장 중요하다고 말해야합니다. Object Class Clone ()의 기본 방법을주의 깊게 관찰하면 기본 방법의 효율은 일반적으로 Java의 비 천연 방법보다 훨씬 높습니다. 또한 첫 번째 새 A 클래스 대신 객체에서 Clone () 메소드를 사용한 다음 원본 객체에서 정보를 새 개체에 할당 해야하는 이유를 설명하지만 클론 기능도 구현됩니다. 두 번째 지점에서는 객체 클래스의 Clone ()가 보호 된 속성이있는 방법인지 여부를 관찰해야합니다. 이것은 또한 clone () 메소드를 적용하려면 객체 클래스를 상속해야한다는 것을 의미합니다. Java에서는 모든 클래스가 기본적으로 객체 클래스를 상속하므로 걱정할 필요가 없습니다. 그런 다음 복제 () 메소드를 과부하하십시오. 고려해야 할 또 다른 클래스가 다른 클래스 가이 클론 클래스의 클론 () 메소드를 호출하려면 과부하 후 클론 () 메소드의 속성을 공개해야한다는 것입니다.
그렇다면 클론 클래스가 여전히 클로닝 가능한 인터페이스를 구현하는 이유는 무엇입니까? 복제 가능한 인터페이스에는 어떤 방법도 포함되어 있지 않습니다! 실제로이 인터페이스는 깃발 일 뿐이며이 플래그는 객체 클래스의 클론 () 메소드에만 해당됩니다. 클론 클래스가 클로닝 가능한 인터페이스를 구현하지 않고 객체의 클론 메소드 (즉, super.clone () 메소드가 호출됨)을 호출하면 객체의 clone () 메소드가 ClonenOnsUpportedException 예외를 던집니다.
위는 클론의 가장 기본적인 단계입니다. 성공적인 클론을 완료하려면 "Shadow Clone"및 "Deep Clone"이 무엇인지 이해해야합니다.
Shadow Clone이란 무엇입니까?
패키지 com.zoer.src; 클래스 unclonea {private int i; 공공 불분명 (int II) {i = II; } public void doubleValue () {i *= 2; } public String toString () {return integer.toString (i); }} 클래스 클론브는 복제 가능한 {public int aint; 공공 불분명 unca = New uncca (111); 공개 물체 클론 () {cloneb o = null; {o = (cloneb) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} public class objref {public static void main (String [] a) {cloneb b1 = new Cloneb (); B1.AINT = 11; System.out.println ( "클론 전, b1.aint =" + b1.aint); System.out.println ( "클론 전, b1.unca =" + b1.unca); cloneb b2 = (cloneb) b1.clone (); b2.aint = 22; b2.unca.doublevalue (); System.out.println ( "==================================================================================================================== ====================================================================== ======================================================================== ====================================================================== ======================================================================== ====================================================================== ======================================================================== 클론, b2.aint = " + b2.aint); System.out.println ( "클론 후 B2.unca =" + b2.unca); }} 출력 결과 :
복제 전, B1.AINT = 11 전 클론, B1.unca = 111 =========================================================== =================================================================== ================================================================= =================================================================== ================================================================= =================================================================== ================================================================= ===================================================================
출력 결과는 INT 변수 AINT 및 인스턴스 객체 UNCA의 클론 결과가 일치하지 않음을 보여줍니다. B2의 Aint 변수는 B1의 AINT에 영향을 미치지 않기 때문에 Int 유형은 진정으로 클론입니다. 즉, B2.aint와 B1.aint는 이미 다른 메모리 공간을 차지하고 있습니다. B2.aint는 B1.aint의 실제 사본입니다. 반대로, B2.unca 로의 변경은 B1.unca로 동시에 변경되며 B2.unca와 B1.Unca는 동일한 객체를 가리키는 다른 참조입니다! 이것으로부터 우리는 객체 클래스에서 clone () 메소드를 호출하는 효과가 다음과 같습니다. 먼저 원래 객체와 동일한 메모리에서 공간을 열고 원래 객체의 내용을 그대로 복사합니다. 기본 데이터 유형의 경우 이러한 작업은 문제가되지 않지만 비-프리맨티 유형 변수의 경우 객체에 대한 참조 만 저장하는 것을 알고 있으며, 이는 클론 후 동일한 객체와 해당 변수를 원래 객체의 해당 변수를 가리 키게합니다.
대부분의 경우,이 클론의 결과는 종종 우리가 기대하는 결과가 아니며이 클론을 "섀도 클론"이라고도합니다. b2.unca를 b2.unca와 다른 객체를 가리키고 b2.unca는 B1.unca의 초기 정보로서 정보를 포함하려면 깊이 클론을 구현해야합니다.
딥 클론을 수행하는 방법?
위의 예를 깊은 클론으로 변경하는 것은 매우 간단하며 두 가지 변경이 필요합니다. 하나는 unc undronea 클래스가 CloneB 클래스와 동일한 클론 기능을 구현할 수 있도록하는 것입니다 (클로닝 가능한 인터페이스를 구현하고 클론 메서드를 과부하 시키십시오). 두 번째는 cloneb의 clone () 메소드에 문장 o.unca = (unclonea) unca.clone ()을 추가하는 것입니다.
패키지 com.zoer.src; 클래스 unclonea는 복제 가능한 {private int i; 공공 불분명 (int II) {i = II; } public void doubleValue () {i *= 2; } public String toString () {return integer.toString (i); } public Object Clone () {unclonea o = null; try {o = (unclonea) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} 클래스 클론브는 복제 가능한 {public int aint; 공공 불분명 unca = New uncca (111); 공개 물체 클론 () {cloneb o = null; {o = (cloneb) super.clone (); } catch (clonenotsupportedException e) {e.printstacktrace (); } O.unca = (unclonea) unca.clone (); 반품 o; }} public class clonemain {public static void main (String [] a) {cloneb b1 = new Cloneb (); B1.AINT = 11; System.out.println ( "클론 전, b1.aint =" + b1.aint); System.out.println ( "클론 전, b1.unca =" + b1.unca); cloneb b2 = (cloneb) b1.clone (); b2.aint = 22; b2.unca.doublevalue (); System.out.println ( "================================================================================================================================== ============================================================================================================= ============================================================================================================= ======================================================================================================== 클론, b1.aint = " + b1.aint); System.out.println ( "클론 후 B1.unca =" + B1.unca); System.out.println ( "========================================================================================================= ============================================================== ================================================================= ============================================================== ================================================================= ============================================================== ================================================================= 출력 결과 :
복제 전, B1.AINT = 11 전 클론, B1.unca = 111 =========================================================== =================================================================== ================================================================= =================================================================== ================================================================= =================================================================== ================================================================= ===================================================================
B2.unca의 현재 변화는 B1.unca에 영향을 미치지 않는다는 것을 알 수 있습니다. 이때, b1.unca와 b2.unca는 두 개의 다른 냉방 인스턴스를 가리키고 B1과 B2는 클론 B2 = (cloneb) b1.clone (); 여기에서 b1.i = b2.i = 11이라고 불립니다.
모든 클래스가 딥 클론을 구현할 수있는 것은 아닙니다. 예를 들어, 위의 Cloneb 클래스의 unc unc unc unclonea type 변수를 StringBuffer 유형으로 변경하면 JDK API의 StringBuffer에 대한 설명을보십시오. StringBuffer는 Clone () 메소드에 과부하가 걸리지 않으며, 더 심각한 점은 StringBuffer가 여전히 최종 클래스라는 것입니다. 즉, 상속을 사용하여 StringBuffer의 클론을 간접적으로 구현할 수는 없습니다. 클래스에 StringBuffer 유형 객체 또는 StringBuffer와 유사한 객체가 포함 된 경우 두 가지 선택이 있습니다. Shadow Clone 만 구현하거나 클래스의 클론 () 메소드에 문장을 추가 할 수 있습니다 (Sringbuffer 객체라고 가정하고 가변 이름은 여전히 UNCA라고 가정) : O.Unca = New StringBuffer (Unca.Tostring ()); // 원래는 다음과 같습니다. O.unca = (unclonea) unca.clone ();
또한 딥 클론을 자동으로 구현할 수있는 기본 데이터 유형 외에도 문자열 객체는 예외입니다. 클론 후의 성능은 또한 깊은 클론을 구현하는 것으로 보입니다. 이것은 단지 환상 일 뿐이지 만 우리의 프로그래밍을 크게 촉진합니다.
클론에서 문자열과 StringBuffer의 차이점은 String과 StringBuffer의 차이에 중점을 두지 않지만이 예에서는 문자열 클래스의 고유 한 기능을 볼 수 있습니다.
다음 예제에는 두 가지 클래스가 포함됩니다. Clonec 클래스에는 문자열 유형 변수와 StringBuffer 유형 변수가 포함되어 있으며 Clone () 메소드를 구현합니다. Clonec 유형 변수 C1은 Strclone 클래스에서 선언 된 다음 C1의 클론 () 메소드가 호출되어 C1 C2의 사본을 생성합니다. C2에서 문자열 및 StringBuffer 유형 변수를 변경 한 후 결과가 인쇄됩니다.
패키지 com.zoer.src; 클래스 클로 넥은 복제 가능한 {public string str; 공개 StringBuffer strbuff; 공개 물체 클론 () {clonec o = null; {o = (clonec) super.clone ()을 시도하십시오. } catch (clonenotsupportedException e) {e.printstacktrace (); } return o; }} public class strclone {public static void main (string [] a) {clonec c1 = new Clonec (); c1.str = new String ( "Initializestres"); c1.strbuff = new StringBuffer ( "InitializestRbuff"); System.out.println ( "클론 전, c1.str =" + c1.str); System.out.println ( "클론 전, c1.strbuff =" + c1.strbuff); Clonec C2 = (Clonec) C1.Clone (); c2.str = c2.str.substring (0, 5); c2.strbuff = c2.strbuff.append ( "strbuff 클론 변경"); System.out.println ( "=============================================== =========================================================================== =========================================================================== ============================================================================ =========================================================================== =========================================================================== =========================================================================== ============================================================================ System.out.println ( "클론 후 C2.strbuff =" + c2.strbuff); 실행 결과 :
<span style = "font-family : 'microsoft yahei';"> <span style = "font-size : 16px;"> 복제 전, c1.str = c1.strbuff = initializestrbuff ================================================================================================================ ================================================================================================================ ================================================================================================================ ================================================================================================================ 클론, c2.str = Initializestrbuff Change Strbuff 클론 </span> </span>
인쇄 된 결과는 C2.STR에 대한 변경이 C1.Str에 영향을 미치지 않았기 때문에 문자열 유형의 변수가 깊이 클론을 구현 한 것으로 보입니다! Java는 Sring 클래스를 기본 데이터 유형으로 간주합니까? 실제로 이것은 사실이 아닙니다. 여기에 작은 트릭이 있습니다. 비밀은 c2.str = c2.str.substring (0,5)에 있습니다! 본질적으로 C1.Str 및 C2.Str는 클론시 여전히 참조이며 둘 다 동일한 문자열 객체를 가리 킵니다. 그러나 c2.str = c2.str.substring (0,5)이면 새 문자열 유형을 생성 한 다음 C2.Str에 다시 할당하는 것과 같습니다. 문자열은 Sun Engineers가 불변의 클래스로 작성되며 모든 문자열 클래스의 기능은 자신의 값을 변경할 수 없기 때문입니다.
위는이 기사에 관한 것입니다. 모든 사람이 자바에서 깊고 얕은 복제를 이해하는 것이 도움이되기를 바랍니다.