In JAVA, the String class is passed to the function as a formal parameter. After modification, the external reference cannot obtain the changed value.

code example

First, let's take an example to illustrate the content of this study:

public class StringTest {
    
    
    private String aa = "1111";
    private StringBuilder bb = new StringBuilder("bbbb");
    private char ca = 'y';
    private Character cr = 'z';
    private char[] ar = {
    
    'a', 'b', 'c'};

    {
    
    
        System.out.println("这是一个考研");
    }

    static {
    
    
        System.out.println("这是一个考验");
    }

    public static void main(String[] args) {
    
    
        try {
    
    
            StringTest test = new StringTest();
            test.change(test.bb, test.aa, test.cr, test.ca, test.ar, test);
            System.out.print(test.bb);
            System.out.print(test.aa);
            System.out.print(test.ca);
            System.out.print(test.cr);
            System.out.println(test.ar);

            Field field = StringTest.class.getDeclaredField("aa");
            field.setAccessible(true);
            field.set(test, "0000");
            System.out.print(test.bb);
            System.out.print(test.aa);//0   修改String值成功
            System.out.print(test.ca);
            System.out.print(test.cr);
            System.out.println(test.ar);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public void change(StringBuilder bb, String aa, Character cr, char ca, char[] ar, StringTest st) {
    
    
        bb.append(" is Change");
        aa = "1111 AND ";
        cr = new Character('m');
        ca = 'n';
        ar[0] = 'g';
        st.aa = "3333 AND";
    }

}

Output result:
insert image description here
This result indicates that the reassignment of a variable of type String inside the method does not have any effect on the prototype of the String variable. Well, the logic and running results of this example are clearly displayed. Next, let's analyze this small program. Before that, let's review the so-called "pass-by-value" and "pass-by-reference" problems in Java.

The "pass by value" and "pass by reference" problem in Java

Many programmers who are new to Java have thought about this problem, because this is the so-called "C language pass-by-value and pass-by-pointer problem" in the Java language.
The final conclusion is:
***In Java, when the basic type is passed into the method as a parameter, no matter how the parameter is changed in the method, the external variable prototype is always unchanged, because there are external variables inside the method A copy of , and changes to this copy do not change the value of the external variable. ***The code is similar to the example above:

int number = 0;
changeNumber(number) {
    
    number++}; //改变送进的int变量
System.out.println(number); //这时number依然为0

This is called "passing by value", that is, the method operates on the parameter variable (that is, a copy of a value of the prototype variable) and changes only a copy of the prototype variable, not the variable itself. So the variable prototype doesn't change with it.

But when the parameter passed into the method is a non-basic type (that is to say, it is a variable of an object type), when the parameter variable is changed in the method, the variable prototype will also change. The code is also similar to the above example:

StringBuffer strBuf = new StringBuffer(“original”);
changeStringBuffer(strBuf) {
    
    strbuf.apend(“ is changed!)} //改变送进的StringBuffer变量
System.out.println(strBuf); //这时strBuf的值就变为了original is changed! 

This feature is called "pass by reference" , also known as pass by reference, that is, when a method operates a parameter variable, it copies the reference of the variable. Note that the parameter passed to the method is a reference to the variable, which is actually a pointer , and then use this reference to find the real address of the variable (in this case, the object) and operate on it. When the method ends, the parameter variable inside the method disappears. But you must know that this variable is just a reference to the object, it just points to the real address of the object, not the object itself, so its disappearance will not bring any negative effects. Looking back at the prototype variable, the prototype variable is essentially a reference to that object (the same as the parameter variable). The original change to the object pointed to by the parameter variable is simply the change to the object pointed to by the prototype variable. So the object represented by the prototype variable is changed, and the change is preserved.

After understanding this classic problem, many attentive readers will immediately ask new questions: "But the String type is a non-basic type in the Java language! Why is its change in the method not preserved!" Indeed, this is a question, and this new question overturns almost all the conclusions of that classic question. Is that so? OK, now let's move on to the analysis.

One of the misunderstandings about the passing of String parameters - direct assignment and object assignment

When a variable of type String is used as a parameter, how can it be passed by value like a variable of basic type? Some friends have given explanations on this issue.
One explanation is that when assigning a variable of type String, there is no new object, but a string is directly assigned, so Java treats this variable of type String as a basic type. That is, String str = new String("original");, not String str = "original";. Is this the problem because the assignment to String type variables is different? Let's make a slight modification to the previous example and see the result after running it. The modified code is as follows:

private void testB() {
    
    
String originalStr = new String("original");
System.out.println("Test B Begin:");
System.out.println("The outer String: " + originalStr);
changeNewString(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test B End:");
System.out.println();
}

public void changeNewString(String original) {
    
    
original = new String(original + " is changed!");
System.out.println("The changed inner String: " + original);
}

Let's see what the result of this run looks like:

Test B Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test B End.

Practice has proved that this statement is wrong.
In fact, the difference between direct assignment of strings and assignment of objects with new is only in the storage method.
Briefly explain:
When a string is directly assigned, the value referenced by a variable of type String is stored in the constant pool of the class. Because "original" itself is a string constant, on the other hand, String is an immutable type, so this String type variable is equivalent to a reference to a constant. In this case, the size of the variable's memory space is determined at compile time.
The way of the new object is to store the "original" in the memory heap space of the String object, and this storage action is performed at runtime. In this case, Java doesn't treat the "original" string as a constant, because then it appears as a parameter to create the String object.
Therefore, the assignment method of String is not directly related to the problem of passing its parameters. In short, this explanation is not correct.
4. The second misunderstanding of the String parameter passing problem - the difference between "=" variable value and method assignment.
Some friends think that the problem of variable value asynchronous is in the way of changing the value.
This statement believes: "In Java, there are two situations for changing the value of a parameter. The first is to use the assignment number "=" to directly assign the value to change it; the second is to refer to some objects through a certain way. Change its member data, such as through the member method of the object itself. It is believed that for the first case, the change will not affect the data other than the method passed into the parameter variable, or directly will not change the original data. The second method, on the contrary, will affect the source data - because the object pointed to by the reference does not change, and the member data changes, it is the object that actually changes." This view says that the member variables of the class must be used. Change the member data to successfully change the member data.
This method sounds a bit..., we still use the old method, write a demo, make a small test, the code is as follows:

private void testC() {
    
    
String originalStr = new String("original");
System.out.println("Test C Begin:");
System.out.println("The outer String: " + originalStr);
changeStrWithMethod(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test C End.");
System.out.println();
}

private static void changeStrWithMethod(String original) {
    
    
original = original.concat(" is changed!");
System.out.println("The changed inner String: " + original);
}

The result is as follows:

Test C Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test C End.

How, this proves that the problem is not here, so
what is the reason for this situation?
Okay, here's my explanation.

The real reason for this problem is because the storage of the String class is through the final modified char[] array to store the result. Cannot be changed. So every time a reference of type String from the outside is passed into the method, it just passes the reference of the variable of type String from the outside to the method parameter variable. correct. External String variables and method parameter variables are just references to actual char[] arrays. So when we change the reference of this parameter inside the method, because the char[] array cannot be changed, every time we create a new variable, we create a new String instance. Obviously the outer String type variable does not point to the new String instance. So no new changes will be picked up.

The following program routine assumes that tString points to the A memory space, and the A memory space stores the string "hello", and then calls the modst function to assign the tString reference to the text reference. Note that it is a reference. It is indeed pass-by-reference, we know that String is immutable and any operation that changes it will result in a new String instance. So in the method, text points to the B space, and the B space stores the "sdf" string, but at this time, tString still points to the A space, and does not point to the B space.

From the String source code, we can know the following information:
a) String is a final class, because it is a final modified class, it cannot be inherited, and there is no such thing as rewriting.
b) The actual storage of the string is an array, and it is final modified, and the memory address does not change after the space is allocated.
c) All member variables are private final modified, and no corresponding XXXSetter method is provided, these fields are not allowed to be modified externally, and they can only be assigned once.
d) The operations involving the value array (only part of the source code is provided above) all use the method of copying the elements of the array, which ensures that the character array cannot be modified internally,
so String is immutable after initialization.

How to Modify the Value of an Initialized String
Even an immutable class can still change the value of its properties through reflection. Java reflection subverts all conventional Java theory.
IllegalArgumentException - if the specified object is not an instance of the class or interface that declares the underlying field (or its subclasses or implementers), or the unpacking conversion fails. Because the JVM optimizes the final type of String at compile time, it will treat String as a constant at compile time. , so the String aa = "1111" value cannot be modified directly, but by an instance of the class or interface that declares the underlying field (or its subclasses or implementers) to modify String aa = "1111".

How can I customize an immutable class?
To sum up, how can I customize an immutable class?
1) The class is modified with the final modifier
2) All fields of the class are modified with private final
3) The XXXSetter method is not provided, and the getXXX method returns the copied object, not the object itself.
4) When the constructor initializes member variables, use deep copy.

What is a deep copy?
'A deep copy is an entire independent copy of an object, a deep copy copies all attributes, and copies the dynamically allocated memory pointed to by the attributes. A deep copy occurs when an object is copied along with the objects it references. Deep copies are slower and more expensive than shallow copies. In short, a deep copy copies all the objects referenced by the object to be copied.

How to implement deep copy?
A class that implements object copying must implement the Cloneable interface and override clone().
Note: If the Cloneable interface is not implemented, a CloneNotSupportedException runtime exception will occur.
Example:

/*
* 实现深拷贝
* */
class Teacher implements Cloneable {
    
    
    private String name;
    private int age;


    public String getName() {
    
    
        return name;
    }


    public void setName(String name) {
    
    
        this.name = name;
    }


    public int getAge() {
    
    
        return age;
    }


    public void setAge(int age) {
    
    
        this.age = age;
    }
    public Object clone() throws CloneNotSupportedException
    {
    
    
        return super.clone();
    }
}
class Student_One implements Cloneable{
    
    
    private String name;
    private Teacher teacher;//添加教师的引用


    public Teacher getTeacher() {
    
    
        return teacher;
    }


    public void setTeacher(Teacher teacher) {
    
    
        this.teacher = teacher;
    }


    public String getName() {
    
    
        return name;
    }


    public void setName(String name) {
    
    
        this.name = name;
    }


    @Override
    public Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();//调用obeject的clone默认为浅拷贝
    }
}
class Student_Two implements Cloneable{
    
    
    private String name;
    private Teacher teacher;//添加教师的引用


    public Teacher getTeacher() {
    
    
        return teacher;
    }


    public void setTeacher(Teacher teacher) {
    
    
        this.teacher = teacher;
    }


    public String getName() {
    
    
        return name;
    }


    public void setName(String name) {
    
    
        this.name = name;
    }


    @Override
    public Object clone() throws CloneNotSupportedException {
    
    
        //return super.clone();//调用obeject的clone默认为浅拷贝
        Student_Two student = (Student_Two) super.clone();
        student.setTeacher((Teacher) student.getTeacher().clone());//T复制一份eacher对象并重新set进来
        return student;
    }
}
public class StringDemo1 {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        //新建一个老师对象
        Teacher teacher = new Teacher();
        teacher.setAge(25);
        teacher.setName("李华");
        Student_One student_one = new Student_One();
        student_one.setName("同学甲");
        System.err.println();
        student_one.setTeacher(teacher);
        //拷贝一个Student_One对象(浅拷贝)
        Student_One student_one1 = (Student_One)student_one.clone();
        System.err.println(student_one1.getTeacher().getName());//李华  原拷贝对象
        //修改老师的名字,会把拷贝的对象的老师名称也一同修改了,因为它们指向的是同一块地址,也就是同一个对象
        teacher.setName("黄珊");
        System.err.println(student_one1.getTeacher().getName());//黄珊


        //重新设置老师名为为李华
        teacher.setName("李华");
        Student_Two student_two = new Student_Two();
        student_two.setTeacher(teacher);
        student_two.setName("同学乙");
        //拷贝一个Student_Two对象
        Student_Two student_two1 = (Student_Two) student_two.clone();
        System.err.println(student_two1.getTeacher().getName());//李华  原拷贝对象
        //修改老师的名字,打印发现并没有影响原拷贝对象的值,所以为深拷贝,是不同的两个对象
        teacher.setName("黄珊");
        System.err.println(student_two1.getTeacher().getName());//李华
    }
}

Guess you like

Origin blog.csdn.net/GBS20200720/article/details/124168234