Let's talk briefly about parameter passing of type String in Java

  • 2020-04-01 04:28:58
  • OfStack

Abstract: from the perspective of implementation principle, this article explains and analyzes the "non-object" feature of String type variables as method parameters in the Java language.

First, the initial example

The most important thing in writing code is practice, and a statement without trial and error is just a figment of the imagination. So, in this article, I'll start by throwing around the core topic with a simple example:


public class StringAsParamOfMethodDemo { 
    public static void main(String[] args) { 
       StringAsParamOfMethodDemo StringAsParamOfMethodDemo = new StringAsParamOfMethodDemo(); 
       StringAsParamOfMethodDemo.testA(); 
    } 
    private void testA() { 
       String originalStr = "original"; 
       System.out.println("Test A Begin:"); 
       System.out.println("The outer String: " + originalStr); 
       simpleChangeString(originalStr); 
       System.out.println("The outer String after inner change: " + originalStr); 
       System.out.println("Test A End."); 
       System.out.println(); 
    } 
    public void simpleChangeString(String original) { 
       original = original + " is changed!"; 
       System.out.println("The changed inner String: " + original); 
    } 
} 

The logic of this code is as follows: assign a local variable of type String, then send the variable as an argument to a method, in which the value of the variable is changed. After compiling and running, the output looks like this:


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

This result shows that the reassignment operation inside the method on a variable of type String does not have any effect on the prototype of the variable. Now that the logic and results of the example are clear, let's examine the applet. Before we do that, let's review the so-called "pass value" and "pass reference" issues in Java.

Two, in Java "pass value" and "pass reference" problem

Many Java novice programmers have pondered this issue because it is the so-called "C pass-through and pass-through problem" in the Java language.

The final conclusion is:

In Java, when a primitive type is passed into a method as a parameter, the external variable prototype is always the same no matter how the parameter is changed within the method. The code is similar to the above example:


int number = 0; 

changeNumber(number) {number++}; //Change the int variable sent in

System. The out. Println (number); // the number is still 0
This is called "value passing," where a method operates on a parameter variable (a copy of a value of a prototype variable) and changes only a copy of the prototype variable, not the variable itself. So the variable prototype doesn't change.

However, when the parameter passed in by the method is of a non-basic type (that is, a variable of an object type), the method changes the parameter variable and the variable prototype changes accordingly. The code is also similar to the above example:


StringBuffer strBuf = new StringBuffer( " original " ); 

changeStringBuffer(strBuf) {strbuf.apend( "  is changed! " )} //Changes the StringBuffer variable sent in

System. The out. Println (strBuf); // at this point the value of strBuf becomes original is changed!      
This feature is called "reference passing," or addressing, where a method operates on a parameter variable by copying a reference to the variable, then using the reference to find the actual address of the variable (in this case, the object) and manipulate it. When the method ends, the argument inside the method disappears. But remember 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 doesn't have a negative effect. Going back to the prototype variable, the prototype variable is essentially a reference to that object (the same as the argument variable), and a change to the argument variable is a change to the argument variable. So the object that the prototype variable represents is changed, and the change is preserved.

Knowing this classic problem, many attentive readers will immediately raise a new question: "but String is a non-primitive type in the Java language. Why haven't its changes in methods been preserved?" Indeed, it is a problem, and the new question reverses almost all the conclusions of the classic question. Is that so? All right, now we're going to continue our analysis.

3. Distortion of String parameter passing One - direct assignment to objects

How can a variable of type String be passed as a parameter like a primitive variable? On this question, some friends have given an explanation, but unfortunately it is not correct.
One explanation is that String variables are assigned not as new objects, but as strings, so Java treats them as primitive types. That is, String STR = new String(" original "); , instead of String STR = "original"; . Is that the problem? Let's tweak the previous example and see what happens when we run 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 take a look at the results:


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 this wrong.

In fact, the only difference between a direct string assignment and an object assigned with new is the way it is stored.

Briefly:

When a String is directly assigned, the value referenced by a variable of type String is stored in the constant pool of the class. Since original itself is a String constant, and String is an immutable type, the string-type variable is a reference to a constant. In this case, the size of the variable's memory space is determined at compile time.

The new object does this by storing the original in the String object's memory space, and this storage action is done at runtime. In this case, Java does not treat the String original as a constant because it appears as an argument to the String object.

So there is no direct connection between the way to assign a String and the problem of passing its parameters. Anyway, this explanation is not a positive solution.

4. Distortion of String parameter passing Two -- "=" change value and method change value

Some friends think that the problem with changing values out of sync is in the way they are changed.

This statement states: "in Java, there are two ways to change the value of an argument. Second, for some object's reference, through certain ways to its member data, such as through the object's own method. In the first case, the change does not affect data that is passed in outside the method of the argument variable, or simply the source data. The second method, by contrast, affects the source data -- because the object that the reference indicates has not changed, the change to its member data is essentially the object that changed.

This way may sound a little... , we still use the old way, write a demo, do a small experiment, 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 results are 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.

Well, this proves that the problem is not here, and yet another explanation dies under the weight of practical arguments.

So what's causing this?

Now, without further ado, here is my explanation.

V. the crux of the problem of String parameter passing

In fact, the most direct way to really understand a class or API framework is to look at the source code.

Now let's take a look at the new String object (in the String class), which is the constructor of the String class:


  public String(String original) { 
        int size = original.count; 
        char[] originalValue = original.value; 
        char[] v; 
        if (originalValue.length > size) { 
          // The array representing the String is bigger than the new 
          // String itself.  Perhaps this constructor is being called 
          // in order to trim the baggage, so make a copy of the array. 
          int off = original.offset; 
              v = Arrays.copyOfRange(originalValue, off, off+size); 
        } else { 
          // The array representing the String is the same 
          // size as the String, so no point in making a copy. 
          v = originalValue; 
        } 
        this.offset = 0; 
        this.count = size; 
        this.value = v; 
}

You may have noticed the char[] inside, which means that the storage of strings is actually implemented by char[]. How's that ? In fact, is a layer of window paper. I don't know if you remember the wrapper classes for the basic types defined in the Java API. For example, Integer is an int wrapper class, Float is a wrapper class for Float, and so on. The operations on the values of these wrapper classes are actually performed on their corresponding primitive types. Is there a feeling? Yes, String is the same thing as a wrapper class for char[]. One of the qualities of a wrapper class is that its values are manipulated to reflect the properties of its corresponding primitive types. This is how the wrapper class is represented when arguments are passed. So, the interpretation of String's rendering in this case is a natural one. Similarly, wrapper classes such as Integer, Float, etc., and String behave the same in this case. The specific analysis is omitted here.

This is the real reason why stringbuffers are recommended when manipulating strings in different ways. As for why StringBuffer does not behave like a String, you will see from the implementation of StringBuffer that it will not be repeated here.

Write at the end

Thus, the principle of parameter passing of String type is presented. In fact, as long as the right way of analysis, thinking will eventually come to the right conclusion.
There are two foundations for correct analytical methods:

1, more practice: the hand must not make lazy, practice will give true knowledge.

2, based on the principle: make clear the program logic of the most direct and simple way is to see the source code, this is no doubt.

As long as the analysis based on these two foundations, in many cases will achieve twice the result with half the effort. As a rule of thumb, this is one of the "shortcut" ways to analyze a program.


Related articles: