One advantage of Java language is to cancel the concept of pointers, but also leads to many programmers to ignore the difference between objects and references in programming. This article will try to clarify this concept. And since Java cannot solve the problem of object replication by simple assignment, the Clone () method is often used to copy the object during development. This article will let you know what is shadowclone and depth clone, knowing their difference, advantages, and disadvantages.
Seeing this title: Java language clearly explains the cancellation of the pointer, because the pointer is often the root cause of the unsafe code, and it will make the program very complicated and difficult to understand, abuse The code written by the pointer is not on the use of the "GOTO" statement that has long been notorious. The concept of Java waives the pointer is definitely extremely wise. But this is only a clear pointer definition in the Java language, essentially every New statement returns a reference to a pointer, but most of the time Java does not have to do this "pointer" in Java, not like operation C The pointer is like a steeper. The only thing to care is to pass the object to the function. The following routines:
package reference; class Obj {String str = "init value"; public String toString () {return str;}} public class ObjRef {Obj aObj = new Obj (); int aInt = 11; public void changeObj (Obj inObj) { INOBJ.STR = "Changed Value";} public void changePri (int inint) {ININT = 22;} public static void main (string [] args) {objref oref = new objref (); system.out.println ("Before Call changeObj () method: " oref.aobj); oref.changeobj (oref.aobj); System.out.Println (" After Call ChangeObj () Method: " OREF.AOBJ); System.out.Println (" ========================================= "); System.out.Println (" Before Call Changepri () Method: " oref.aint); Oref.changepri (Oref.Aint); System.out.Println (" After Call ChangePri () Method: " OREF.AINT);}} / * Run Result Before Call ChangeObj () Method: init value at call change value ====================================== == Before call changepri () Method: 11After call changePri () Method: 11 * * / This code is called two very similar methods, ChangeObj () and C HangePri (). The only difference is that they use objects as input parameters, and another basic type INT in Java as input parameters. And the input parameters are modified inside the two functions. It seems that the results of the program output are not the same. The changeObj () method really changes the parameters of the input, and the ChangePri () method does not change any changes to the parameters of the input.
From this example, you know that Java is different for objects and basic data types. As in the C language, when the basic data type of Java (such as int, char, double) is transmitted to the function body as an entry parameter, the incoming parameter turns a local variable inside the function body, this local variable is input. One copy of the parameter, all the operations inside all the functions are for this copy, after the function is executed, the local variable has completed its mission, it does not have a variable as an input parameter. The parameter transmission of this way is called "value transfer". In Java's transmission as an entrance parameter, the default is "reference delivery", that is to say only one "reference" of the object, this "reference" concept is the same as the pointer in the C language. . When the input variable is changed inside the function, it is essentially direct operation of this object. In addition to the "reference delivery" when the function is transmitted, it is "reference delivery" when any "=" to the object variable. Such as:
package reference; class PassObj {String str = "init value";} public class ObjPassValue {public static void main (String [] args) {PassObj objA = new PassObj (); PassObj objB = objA; objA.str = "changed in Obja "; System.out.Println (" Print Objb.str value: " objb.str);}} / * Run Result Print Objb.str value: Changed in obja * /
The first sentence generates a new Passobj object in memory, then assigns this passobj to the variable OBJA, the second sentence is to assign the reference to the passobj object to the variable OBJB. At this time, OBJA and OBJB are two fully consistent variables, and any changes to OBJA will be equivalent to OBJB.
Even if you understand that the "pointer" concept in the Java language may not deliver the mistakes in the end.
Is HashTable really stores an object?
Take a look at the very simple code below, first declare a HashTable and StringBuffer object, then put the striingbuffler object in the HashTable table, some new StringBuffer objects in the HashTable table. String:
package reference; import java.util *;. public class HashtableAdd {public static void main (String [] args) {Hashtable ht = new Hashtable (); StringBuffer sb = new StringBuffer (); sb.append ( "abc,") Ht.Put ("1", SB); sb.append ("DEF,"); HT.PUT ("2", SB); sb.append ("mno,"); ht.put ("3" , SB); sb.append ("xyz."); ht.put ("4", sb); int Numobj = 0; enumeration it = ht.elements (); whose (it.hasmorelements ()) {system. Out.print ("Get StringBufffer" "from hashtable:"); system.out.println (it.nextelement ());}}} If you think the result is: Get StringBufffer 1 from Hashtable: ABC, GET STRINGBUFFFER 2 from Hashtable: ABC, DEF, GET STRINGBUFFFER 3 from Hashtable: ABC, DEF, MNO, Get StringBufffr 4 from Hashtable: ABC, DEF, MNO, XYZ.
Then you have to go back and take a closer look at the previous problem, transmit the object as an entry parameter to the function, essentially to pass the object's reference, and pass the StringBuffer object to the HashTable StringBuffer only passed the reference! Every time the PUT in the HashTable table is StringBuffer, there is no new StringBuffer object, just put a reference to the same StringBuffer object in the HashTable table.
Any StringBuffer object stored in the HashTable table (more exactly the reference to the object) is actually a change in the same "StringBuffer". So HashTable does not really store an object, but can only store references to the object. It should also be known that this principle is the same as the vector, list, map, set, etc. similar to HashTable.
The actual output of the above routine is:
/ * RUN RESULT get StringBufffer 1 from Hashtable: abc, def, mno, xyz get StringBufffer 2 from Hashtable:. Abc, def, mno, xyz get StringBufffer 3 from Hashtable:.. Abc, def, mno, xyz get StringBufffer 4 from Hashtable: ABC, DEF, MNO, XYZ. * /
Class, object and reference
Java's most basic concept is class, classes include functions and variables. If you want to apply classes, you have to generate an object, which is called "instantiation of class". There are several ways to instantiate the classes into objects, the most commonly used "New" operator. After the class instantiates, it means that it is necessary to occupy an instance of a spatial storage in memory. Want to apply to this spatial operation to be applied to an object. References in the Java language are variables, and the type of variable is the object of this reference. Although the function or variable of the object can be called directly after generating an object, such as: New String ("Hello NDP")). Substring (0, 3) // Return Result: Hel
However, because there is no corresponding reference, the use of this object can only limit this statement.
Generation: The reference is always automatically generated during the process of "delivery", and does not require artificial production, and cannot be generated. This transmission includes the case where the object is used as the entry parameters of the function, and includes when the object is assigned with "=". Range: Only a partial reference, no partial object. The refer to the embodiment of the Java language is the variable, and the variables are scope in the Java language, which can be partial or global. Survival: The program can only control the survival cycle of the reference. The survival of the object is controlled by Java. Generate a new object with the "New Object () statement, is declared in the computer's memory, only the Java garbage collector can determine the memory occupied by the object when appropriate. There is no way to prevent changes to references.
What is "clone"?
In the actual programming process, we often have to encounter this situation: there is an object A. Some valid values have been included in a certain time A. At this time, one and a completely the same new object B, and after this B Any change does not affect the value in A, that is, A and B are two separate objects, but the initial value of B is determined by the object. In the Java language, use a simple assignment statement cannot meet this need. To meet this needs, although there are many ways, implementing a clone () method is the easiest and most efficient means.
All classes of Java inherit the java.lang.object class by default, there is a method clone () in the java.lang.Object class. Description Documentation of the JDK API explains that this method will return a copy of the Object object. There are two points to explain: First, copy object returns a new object, not a reference. Second, the difference between copy objects and new objects returned by New operator is that this copy already contains some information about objects, not the initial information of the object.
How to apply a clone () method?
A typical call clone () code is as follows:
class CloneClass implements Cloneable {public int aInt; public Object clone () {CloneClass o = null; try {o = (CloneClass) super.clone ();} catch (CloneNotSupportedException e) {e.printStackTrace ();} return o; }
There are three things that are worth noting. First, it is hoped that the CloneClass class that enables Clone features implements the Cloneable interface. This interface belongs to the java.lang package, and the java.lang package has been in the default import class, so it is not necessary to write java.lang. .Cloneable. Another worth noting note is to overload the clone () method. Finally, Super.Clone () is called in the clone () method, which means that in the case of the inheritance structure of the Clone class, super.clone () directly or indirectly calls the clone () of the java.lang.object class () method. Let's explain these points in detail below. It should be said that the third point is the most important thing, carefully observe a Native method of the Object class, the Native method is generally much higher than the Native method in Java. This also explains why use the client () method instead of first NEW, then assigen the information in the original object to the new object, although this also implements the Clone function. For the second point, it is also necessary to observe the clone () in the Object class or a protected property. This also means that if you want to apply the Clone () method, you must inherit the Object class. All classes in Java are the default inherited Object class, so there is no need to care. Then reload the clone () method. Another point to consider that in order to allow other classes to call the Clone () method of this Clone class, set the properties of the Clone () method to public.
So why does the Clone class also implement the Cloneable interface? Take a little more, the Cloneable interface does not contain any way! In fact, this interface is just a logo, and this flag is just a clone () method in the object class. If the Clone class does not implement the Cloneable interface, and call the Object's clone () method (that is, call super.clone. ) Method), then Object's Clone () method will throw CloneNotSupportedException.
The above is the most basic step of Clone, you want to complete a successful Clone, what else is "Shadow Clone" and "Depth Clone".
What is shadow clone?
The following example contains three classes of UNCLONEA, Cloneb, Clonemain. The Cloneb class contains an instance of UNCLONEA and an int type variable, and overloads the clone () method. The CloneMain initializes an instance B1 of the UNCLONEA class and then calls the clone () method to generate a copy B2 of B1. Finally examine the output of B1 and B2:
Package clone; class unclonea {private Int i; public unclonea (int II) {i = II;} public void doubleValue () {i * = 2;} public string toString () {return integer.tostring (i);}} class CloneB implements Cloneable {public int aInt; public UnCloneA unCA = new UnCloneA (111); public Object clone () {CloneB o = null; try {o = (CloneB) super.clone ();} catch (CloneNotSupportedException e) { E.PrintStackTrace ();} returno}} public class clonemain}} public class clonemain}} public class clonemain}} public class clonema}} public class clonemain}} public static void main (string [] a) {cloneb b1 = new cloneb (); b1.aint = 11; system.out.println ("Before Clone , b1.aint = " b1.aint); System.out.println (" Before Clone, B1.Unca = " B1.Unca); Cloneb B2 = (Cloneb) b1.clone (); b2.aint = 22 B2.Unca.doublevalue (); system.out.println ("==================================" SYSTEM.OUT.Println ("After Clone, B1.Aint =" B1.AINT); System.out.Println ("After Clone, B1.Unca = B1.Unca); System.out.Println "============================================="); System.out.Println ("After Clone, B2.AINT = " b2.aint); system.out.printl n ("After Clone, B2.Unca =" b2.unca);}} / ** Run Result: Before Clone, B1.Aint = 11 Before Clone, B1.Unca = 111 ========= =========================After clone, b1.aint = 11After Clone, b1.unca =
222 =========================================After clone, b2.aint = 22 after clone, b2.unca = 222 * / Output Description INT type Variables AINT and UNCLONEA instance object UNCA's clone results are inconsistent, the int type is true by Clone, because changed the AINT variable in B2, there is no impact on B1's AINT, that is Say, b2.aint and b1.aint have occupied different memory space, B2.Aint is a true copy of B1.Aint. On the contrary, the change of B2.Unca changed B1.Unca at the same time, it was obvious, B2.Unca and B1.Unca were just different references to the same object! As can be seen, the effect generated by the clone () method in the call Object class is: first open a piece of space and the original object, and then copy the contents of the original object. For basic data types, such an operation is no problem, but for non-basic type variables, we know that they are only referenced by the object, which also leads to the non-basic type variable after Clone and the corresponding variables in the original object. Is the same object.
In mostth, the result of this clone is often not what we hope, and this clone is also known as "shadow clone." To point B2.Unca points to different objects with b2.unca, and B2.Unca also contains information in B1.Unca as an initial information, it is necessary to achieve depth Clone.
How to depth clone?
Change the above example to depth clone is simple, need two changes: First, let UNCLONEA classes also implement the same clone function as the Cloneb class (implement the Cloneable interface, overload the Clone () method). The second is to add a sentence unca = (UNCLONEA) in the cloneb clone ();
The procedure is as follows:
package clone.ext; class UnCloneA implements Cloneable {private int i; public UnCloneA (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;}} class CloneB implements Cloneable {public int aInt; public UnCloneA unCA = new UnCloneA (111); public Object clone () {CloneB o = null; try {o = (CloneB) super.clone ();} catch (CloneNotSupportedException e) {e.printStackTrace ( }} o.Unca = (unclonea) unca.clone (); returno;}} public class clonemain} public static void main (string [] a) {cloneb b1 = new cloneb (); b1.aint = 11; System.out.println ("Before Clone, B1.Aint =" B1.AINT); System.out.Println ("Before Clone, B1.Unca =" B1.Unca); Cloneb B2 = (Cloneb) B1. Clone (); b2.aint = 22; b2.unca.doublevalue (); system.out.println ("========================= ======== "); System.out.println (" After C LONE, B1.AINT = " b1.aint); System.out.Println (" After Clone, B1.Unca = " b1.unca); System.out.Println (" ========= ========================== "); System.out.Println (" After Clone, B2.Aint = " B2.AINT); System. Out.println ("After Clone, B2.Unca =" b2.unca);}} / ** Run Result: Before Clone, B1.Aint = 11 Before Clone, B1.Unca =
111 =========================================After clone, b1.aint = 11After Clone, b1.unca = 111 ========================================== after clone, b2.aint = 22After Clone, b2.unca = 222 * / It can be seen that the change of B2.Unca is now affecting B1.Unca. At this time, B1.Unca points to B2.Unca points to two different UNCLONEA instances, and in Cloneb B2 = (Cloneb) b1.clone (); the top of B1 and B2 calls have the same value, here, B1.I = B2.I = 11.
To know that not all classes can achieve depth Clone. For example, if you change the UNCLONEA type variable in the above Cloneb class to the StringBuffer type, look at the instructions about StringBuffer in the JDK API, StringBuffer does not overload the clone () method, more serious is StringBuffer or a Final class, this is also Say we can't indirectly use the inheritance of the StringBuffer Clone. If a class contains a StringBuffer type object or a similar class similar to StringBuffer, we have two options: Either you can only implement shadow clone, or add a sentence in the CLONE () method (assuming is a SRingBuffer object, but also variables Name is still UNCA): o.Unca = new stringbuffler (unca.tostring ()); // The original is: o.unca = (unclonea) unca.clone ();
It is also important to be an exception to the basic data type, and the String object is an exception. It is a depth clone after the Clone, although this is just a description, but it is greatly convenient for our programming.
The difference between String and StringBuffer in Clone
It should be noted that this is not a focus on the difference between String and StringBuffer, but it can also see some of the String class in this example.
The following example includes two classes, the Clonec class contains a string type variable and a StringBuffer type variable, and the clone () method is implemented. Declaring the clonec type variable C1 in the STRCLONE class, then call the C1 CLONE () method to generate C1 C2, and then print results after the String and StringBuffer type variables in C2 are changed:
package clone; class CloneC implements Cloneable {public String str; public StringBuffer strBuff; public Object clone () {CloneC o = null; try {o = (CloneC) super.clone ();} catch (CloneNotSupportedException e) {e.printStackTrace ()} returno}} public class strclone {public static void main (string [] a) {clonec c1 = new clonec (); c1.str = new string ("initializestr"); c1.strbuff = new StringBuffer ("INITIALIZESTRBUFF"); System.out.println ("Before Clone, C1.Str =" C1.Str); System.out.Println ("Before Clone, C1.StrBuff); Clonec C2 = (Clonec) c1.clone (); c2.str = c2.str.substring (0,5); c2.strbuff = c2.strBuff.Append ("Change strbuff clone"); system.out.println ("= ================================= "); System.out.Println (" After Clone, C1.STR = " C1.STR); System.out.Println ("After Clone, C1.StrBuff); System.out.Println (" ================ ================== "); System.out.Println (" After Clone, C2.Str = " C2.Str); System.out.Println (" After Clone , c2.strbuff = " c2.strbuff); }} / * Run Result Before Clone, C1.Str = Initializestr Before Clone, C1.StrBuff = InitializestrBuff ============================ ===== after clone, c1.str =
InitializestrAfter Clone, C1.StrBuff = InitializestrBuff Change strbuff Clone ===================================================After Clone, C2. Str = INITI AFTER Clone, C2.StrBuff = InitializestrBuff Change Strbuff Clone * * / Print results It can be seen that the String type variable seems to have implemented depth Clone, because the changes to C2.STR do not affect C1.STR ! Is Java regards SRING class as a basic data type? In fact, there is a small trick here, the secret is C2.Str = c2.str.substring (0, 5) this statement! In essence, C1.STR and C2.Str are still referenced during Clone, but also points to the same String object. However, when C2.Str = C2.Str.SUBSTRING (0, 5) is executed, its effect is equivalent to generating a new String type and then assigns it to C2.STR. This is because String is written by Sun's engineer as an immutable class, and the function in all String classes cannot change its own value. A simple example is given below:
Package clone; public class strtest {public static void main (string str1 = "this is a test for immutable"; string str2 = str1.substring (0,8); system.out.println ("Print STR1: " STR1); System.out.Println (" Print Str2: " Str2);}} / * Run Result Print Str1: This Is A Test for Immutable Print Str2: this is * /
In the example, although STR1 calls the substring () method, the value of STR1 has not changed. The same is true for other methods in the String class. Of course, if we put the two statements in the top example
C2.Str = C2.Str.SUBString (0,5); c2.strbuff = c2.strbuff.append ("Change Strbuff Clone");
Change it to the following:
C2.Str.Substring (0,5); c2.strbuff.append ("Change Strbuff Clone");
Remove the process of re-assigning, C2.STR can't change, our tricks will be stuffed. But only call during programming
C2.Str.substring (0,5);
There is no meaning of statements.