One advantage of Java is that it removes the pointer concept, but many programmers often ignore the difference between objects and references in programming. This article will try to clarify this concept. In addition, Java cannot solve the problem of object Replication through simple assignment. during development, the clone () method is often used to copy objects. This article will show you what is Shadow Clone and deep clone, and their differences, advantages, and disadvantages.
Is it a bit confusing to see this title: the Java language clearly indicates that the pointer is removed, because the pointer is often convenient and also the root cause of code insecurity, at the same time, it will make the program very complex and difficult to understand. abuse the code written by pointers is no less than using the notorious "Goto" statement. It is absolutely wise for Java to discard the pointer concept. However, there is no explicit pointer definition in the Java language. In essence, every new statement returns a pointer reference, but in most cases, Java does not need to care about how to operate this "Pointer ", don't be as scared as if you were operating on the C ++ pointer. The only thing you need to care about is when passing objects to functions. Example:
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 ("========================== print primtive ===================== ");
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
After call changeobj () method: changed Value
======================== Print primtive ==============================
Before call changepri () method: 11
After call changepri () method: 11
*
*/
The main part of this Code calls two very similar methods: changeobj () and changepri (). The only difference is that they use an object as the input parameter, and the other uses the basic type int in Java as the input parameter. The input parameters are modified within the two function bodies. The output results of the program are not the same in the same way. The changeobj () method actually changes the input parameters, while the changepri () method does not change the input parameters.
From this example, we know that Java processes objects differently from basic data types. Similar to C, when Java's basic data types (such as int, Char, and double) are passed as entry parameters to the function body, the input parameter is changed to a local variable in the function body. This local variable is a copy of the input parameter. all operations in the function body are for this copy operation, after the function is executed, this local variable completes its mission and does not affect the variables used as input parameters. Parameter transfer in this way is called "value transfer ". In Java, the passing of objects as entry parameters is "reference transfer" by default, that is, only one "Reference" of objects is passed ", the concept of "Reference" is the same as that of pointer reference in C language. When the function body changes the input variable, it is essentially a direct operation on this object.
Except for "reference transfer" when the function transfers values, it is "reference transfer" when "=" is used to assign values to object variables ". For example:
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 is to generate a new passobj object in the memory, and then assign the reference of this passobj to the variable obja. The second sentence is to assign the reference of the passobj object to the variable objb. In this case, obja and objb are two completely consistent variables. Any changes to obja in the future are equivalent to changes to objb.
Even if you understand the "Pointer" concept in the Java language, you may also inadvertently make the following mistakes.
Can hashtable really store objects?
Let's take a look at the following simple code. First, we declare a hashtable and stringbuffer object. Then, we put the striingbuffer object into the hashtable table four times and append () The stringbuffer object before each put () some new strings:
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 ();
While (it. hasmoreelements ()){
System. Out. Print ("Get stringbufffer" + (++ numobj) + "from hashtable :");
System. Out. println (it. nextelement ());
}
}
}
If you think the output result is:
Get stringbufffer 1 from hashtable: ABC,
Get stringbufffer 2 from hashtable: ABC, def,
Get stringbufffer 3 from hashtable: ABC, def, MnO,
Get stringbufffer 4 from hashtable: ABC, def, MnO, XYZ.
Then you have to go back and take a closer look at the previous problem. When you pass the object as an entry parameter to the function, it actually passes the object reference, passing the stringbuffer object to hashtable also only transmits the reference of this stringbuffer object! Every time a stringbuffer is put to the hashtable table, no new stringbuffer object is generated, but a reference pointing to the same stringbuffer object is put in the hashtable table.
The changes to any stringbuffer object stored in the hashtable table (more specifically, the object should be referenced) are actually changes to the same "stringbuffer. Therefore, hashtable cannot really store objects, but can only store object references. We should also know that this principle is the same for vector, list, MAP, set, and so on similar to hashtable.
The actual output result of the preceding 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
The most basic concept of Java is class, which includes functions and variables. If you want to apply a class, you must generate an object for the class. This process is called "class instantiation ". There are several methods to convert class instances into objects. The most common method is to use the "new" operator. After the class instance is converted into an object, it means that you need to occupy a space in the memory to store the instance. To operate on this space, you must apply the reference to the object. The reference is a variable in Java, and the variable type is the referenced object. Although the syntax can generate an object and then directly call the functions or variables of the object, for example:
New String ("Hello NDP"). substring (0, 3) // return result: El
However, because there is no reference, the use of this object can only be limited to this statement.
Generation: A Reference always occurs automatically when the object is used as a parameter "pass". It does not need to be produced manually or manually controlled. This transfer includes the use of objects as function entry parameters, and the use of "=" to assign values to objects.
Range: only partial references, no partial objects. Variables are referenced in the Java language, and variables have a range in the Java language. They can be local or global.
Lifetime: The program can only control the referenced lifecycle. The lifetime of an object is controlled by Java. Use the "new object ()" statement to generate a new object. It declares a region storage object in the memory of the computer, only the Java garbage collector can decide to reclaim the memory occupied by objects when appropriate.
There is no way to prevent reference changes.
What is "clone "?
In the actual programming process, we often encounter this situation: there is an object A, which contains some valid values at a time point, in this case, you may need a new object B that is exactly the same as a, and any subsequent changes to B will not affect the value in A. That is to say, A and B are two independent objects, however, the initial value of B is determined by object. In Java, simple assignment statements cannot meet this requirement. Although there are many ways to meet this requirement, the clone () method is the simplest and most efficient method.
By default, all classes in Java inherit the java. Lang. object class, and there is a method clone () in the Java. Lang. Object Class (). This method returns a copy of the object. There are two points to note: first, the copy object returns a new object instead of a reference. Second, the difference between copying an object and the new object returned using the new operator is that this copy already contains information about the original object, rather than the initial information of the object.
How to apply the clone () method?
A typical 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 notable points: first, the cloneclass class that can implement the clone function implements the cloneable interface, which belongs to Java. lang Package, Java. the lang Package is already in the default import class, so you do not need to write it as Java. lang. cloneable. The clone () method is overloaded. Finally, super is called in the clone () method. clone (), which also means no matter what the clone class's inheritance structure is, super. clone () directly or indirectly calls Java. lang. object Class clone () method. The following is a detailed explanation of these points.
The third point is the most important. Take a closer look at the clone () native method of the object class. The efficiency of the native method is generally much higher than that of the non-native method in Java. This also explains why to use the clone () method in the object instead of creating a new class, and then assigning the information in the original object to the new object, although this also implements the clone function. For the second point, you should also observe whether the clone () in the object class is a protected attribute method. This also means that if you want to apply the clone () method, you must inherit the object class. All classes in Java inherit the object class by default, so you don't need to care about this. Reload the clone () method. Another consideration is to allow other classes to call the clone () method of the clone class. After the overload, set the attribute of the clone () method to public.
Why should the clone class implement the cloneable interface? Note that the cloneable interface does not contain any methods! In fact, this interface is only a flag, and this flag is only for the clone () method in the object class. If the clone class does not implement the cloneable interface, it calls the clone () method of the object () method (that is, the super. clone () method), the object's clone () method will throw a clonenotsupportedexception exception.
The above are the most basic steps for clone. to complete a successful clone, you also need to know what is "Shadow Clone" and "Deep clone ".
What is Shadow Clone?
The following example contains three classes: unclonea, cloneb, and clonemain. The cloneb class contains an unclonea instance and an int type variable, and the clone () method is overloaded. The clonemain class initializes an instance B1 of the unclonea class, and then calls the clone () method to generate a copy B2 of B1. Finally, we will 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 ();
}
Return O;
}
}
Public class clonemain {
Public static void main (string [] ){
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. println ("after clone, b2.unca =" + b2.unca );
}
}
/** Run result:
Before clone, b1.aint = 11
Before clone, b1.unca = 111
======================================
After clone, b1.aint = 11
After clone, b1.unca = 222
======================================
After clone, b2.aint = 22
After clone, b2.unca = 222
*/
The output results indicate that the clone results of the int type variable aint and the unclonea Instance Object UNCA are inconsistent. The Int type is actually cloned because the aint variable in B2 is changed, this does not affect aint of B1. That is to say, b2.aint and b1.aint occupy different Memory Spaces. b2.aint is a real copy of b1.aint. On the contrary, changes to b2.unca change b1.unca at the same time. Obviously, b2.unca and b1.unca only point to different references of the same object! It can be seen that the effect of calling the clone () method in the object class is: first open up a space in the memory and the original object, and then copy the content in the original object. For basic data types, such operations are fine, but for non-basic type variables, we know that they only store object references, this also causes the cloned non-basic type variables and the corresponding variables in the original object to point to the same object.
Most of the time, this clone result is often not what we want, and this clone is also called "Shadow Clone ". To direct b2.unca to a different object from b2.unca, and b2.unca must contain the information in b1.unca as the initial information, it is necessary to implement in-depth clone.
How to perform deep clone?
It is very easy to change the above example to deep clone, which requires two changes: first, let the unclonea class also implement the same clone function as the cloneb class (implement the cloneable interface and reload the clone () method ). The second is to add O. UNCA = (unclonea) UNCA. Clone () to the clone () method of cloneb ();
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 ();
Return O;
}
}
Public class clonemain {
Public static void main (string [] ){
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. println ("after clone, b2.unca =" + b2.unca );
}
}
/** Run result:
Before clone, b1.aint = 11
Before clone, b1.unca = 111
======================================
After clone, b1.aint = 11
After clone, b1.unca = 111
======================================
After clone, b2.aint = 22
After clone, b2.unca = 222
*/
We can see that b2.unca changes have no impact on b1.unca. B1.unca and b2.unca point to two different unclonea instances, and at the moment when cloneb b2 = (cloneb) b1.clone () is called, B1 and B2 have the same value. Here, b1. I = b2. I = 11.
You need to know that not all classes can achieve deep clone. For example, if you change the unclonea type variable in the preceding cloneb class to the stringbuffer type, let's take a look at the stringbuffer description in the jdk api. stringbuffer does not overload the clone () method, what's more serious is the stringbuffer or a final class. This also means that we cannot indirectly implement the clone of stringbuffer using an inherited method. If a class contains a stringbuffer type object or an object similar to the stringbuffer class, we have two options: either the Shadow Clone can only be implemented, or the clone () of the class () (assume the sringbuffer object and the variable name is still UNCA): O. UNCA = new stringbuffer (UNCA. tostring (); // The original is: O. UNCA = (unclonea) UNCA. clone ();
We also need to know that in addition to the basic data types that can automatically implement deep clone, the string object is an exception, and it also achieves deep clone after cloning, although this is just an illusion, but it greatly facilitates our programming.
Differences between string and stringbuffer in Clone
It should be noted that the difference between string and stringbuffer is not emphasized here, but some differences between the string class can also be seen from this example.
The following example contains two classes. The clonec class contains a string type variable and a stringbuffer type variable, and implements the clone () method. In the strclone class, declare the clonec type variable C1, and then call the clone () method of C1 to generate a copy of C1 C2, after modifying the string and stringbuffer variables in C2 using the corresponding method, print the result:
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 ();
}
Return O;
}
}
Public class strclone {
Public static void main (string [] ){
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 =" + 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 =" + 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 = initializestr
After clone, c1.strbuff = initializestrbuff change strbuff clone
======================================
After clone, c2.str = initi
After clone, c2.strbuff = initializestrbuff change strbuff clone
*
*/
The printed results show that the variable of the string type has been deeply cloned, because the changes to c2.str do not affect c1.str! Does Java regard the sring class as the basic data type? Actually, there is a little trick here. The secret lies in the c2.str = c2.str. substring () statement! In essence, c1.str and c2.str are still referenced during clone, and both point to the same string object. However, when c2.str = c2.str. substring () is executed, it generates a new string type and then assigns it back to c2.str. This is because the string is written as an immutable class by Sun engineers. Functions in all string classes cannot change their own values. The following is a simple example:
Package clone; public class strtest {public static void main (string [] ARGs) {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 this example, although str1 calls the substring () method, the value of str1 has not changed. Similarly, 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 to the following:
C2.str. substring (0, 5 );
C2.strbuff. append ("Change strbuff clone ");
With the re-assignment process removed, c2.str cannot be changed, and our tricks will show up. However, only
C2.str. substring (0, 5 );
The statement is meaningless.
It should be known that all the basic data types in Java have a corresponding class, such as integer class corresponding to int type, double class corresponding to double type, etc, these classes are the same as those of the string class and cannot be changed. That is to say, all methods in these classes cannot change their own values. This also gives us more options when programming the clone class. At the same time, we can also organize our own classes into unchangeable classes.