This is the 21st day of my participation in Gwen Challenge


1, problem,

Recently, my colleagues encountered the following code problems in pair development:

 private void checkRequest(Request request) throws CommonException {
    checkType(request.getType());
    checkProduct(request.getProducts());
    checkIds(request.getIds());
 }

 private void checkIds(List<String> ids) throws CommonException {
    if (ids.size() > 200) {
        throw new CommonException(CELL_MAX_NUM);
    }
    for (String id : ids) {
        if(isIdInvalid(id)) { ids.remove(id); }}}Copy the code

This method is to check id into the legality, then remove the illegal id, through UT found an error, the original is a for loop in the process of removing elements, will quote ConcurrentModificationException.

2, modify,

The colleague then modifies this code to:

private void checkIds(List<String> ids) throws CommonException {
    if (ids.size() > 200) {
        throw newCommonException(CELL_MAX_NUM); } ids = ids.stream().filter(id -> ! isIdInvalid(Id)).collect(Collectors.toList()); log.print("Valid ids is: " + ids);
}
Copy the code

Is this correct?? Any questions?? Is there a better way??

2.1 to optimize a

First appeared ConcurrentModificationException is due to changes in cycle operation is the size of the List delete failure, general recommendation for other use iterators to replace the foreach loop:

 Iterator<String> iterator = ids.iterator();
    while(iterator.hasNext()){
        String id = iterator.next();
        if(isIdInvalid(id)){ iterator.remove(); }}Copy the code

2.2 optimization of two

If you are using JDK8 or later you can use the removeIf method:

ids.removeIf(this::isIdInvalid);
Copy the code

2.3 optimization of three

If you still want to use the filter method, now look again:

ids.stream().filter(id -> ! isIdInvalid(id)).collect(Collectors.toList());Copy the code

Ids is the method input parameter. After the above processing, IDS filters out invalid ids, but does request. Ids change? This leads to the topic of this article: the passing mechanism of Java method parameters.

3. Value passing? Passing by reference?

Are Java method parameters passed by value or by reference? Java method parameters are passed by value if they are of primitive type and by reference if they are of reference type. This statement is derived phenomenally

The conclusions are as follows:

3.1 Basic Type:

public static void change(int a) {
    a = 50;
    System.out.println("change: " + a);
}

public static void main(String[] args) {
    int a = 10;
    System.out.println("before:" + a);
    change(a);
    System.out.println("after:"+ a); } before:10
	  change:50
	  after:10
Copy the code

3.2 When Referring to object Types:

public static void main(String[] args) {
    Person person = new Person("steven"."18");
    System.out.println("before:" + person);
    change(person);
    System.out.println("after:" + person);
}

public static void change(Person person) {
    person.setName("niu");
    System.out.println("change: "+ person); } before:Person(name= Steven, age=18)
	 change: Person(name=niu, age=18)
	 after:Person(name=niu, age=18)
Copy the code

3.3 Phenomenon and conclusion

From two examples, we can see that changing the method input parameter does not affect the parameters of the method call. When referring to an object as a method parameter, the changes in the method also affect the data outside the method. Again, we need to look at the nature of things as to why changes within the base type method do not affect the outside, while reference types do.

To illustrate these issues, we start with the Java memory model, where JVM memory can be simply divided into heap memory and stack memory.

4. The nature of parameter transfer

The method parameter transfer in Java is similar to that of Sun Wukong in Journey to the West. Sun Wukong copies a fake Sun Wukong, who has the same ability as Sun Wukong and can be beheaded or beheaded. However, no matter what happens to the fake Sun Wukong, the real Sun Wukong will not be affected. Similarly, a copy of the actual parameters of a method is passed in, and the actual parameters themselves are unaffected regardless of what is done to that copy in the method.

4.1 Parameters are basic types

The JVM allocates stack areas for both main and change. A variable in main is passed in to change and assigned to a. In fact, a variable in the change stack is regenerated. Assign a from main to A in the change stack (that is, initialize the change method parameter). In this case, a change in the change method has no effect on ain the main method.

4.2 Parameters are reference types

However, when the reference object is used as a method parameter, the change method is changed, and the calling function is also changed accordingly. At this time, it is easy to create an illusion. Calling the change method is the input parameter itself, rather than its copy, but it is only an illusion.

The program starts with the main method, which creates a Person object and defines the Person variable to point to the Person object, which is different from the basic type. When an object is created, the JVM allocates two pieces of memory, one in the heap to hold the object itself, and the other in the stack to hold reference variables that reference the object. The program then operates on the Person object by reference. Assign the value of “Steven” and “18” to the two member variables name and age of the object, then the memory storage in the system is as follows:

The JVM allocates two stacks of memory for main and change, respectively. When the change method is called, person is passed as an argument to the change method, which also uses value passing. Initialize the person variable by assigning the person variable from main to the change parameter, which holds only a pointer reference to the Person object in main. This reference also points to the same Person object in the main method, with the following memory storage:

This passing of arguments also copies a copy of person and passes it to change, which copies a copy of a reference variable, so when change is modified through the person variable, it still operates on the same person object, on the same object, So when you change person in the change method, you also change person in the main method.

4.3 Redirection within a method

When you point person to another heap in the change method you can see that it doesn’t affect person in the main method at all,

public static void main(String[] args) { Person person = new Person("steven", "18"); System.out.println("before:" + person); change(person); System.out.println("after:" + person); } public static void change(Person person) { person = new Person("niu", "20");; System.out.println("change: " + person); } before:Person(name= Steven, age=18) change: Person(name=niu, age=20) after:Person(name= Steven, age=18)Copy the code

5. Special types

What is the result when the row parameter type is String?

public static void main(String[] args) {
    String name = "steven";
    System.out.println("before:" + name);
    change(name);
    System.out.println("after:" + name);
}

public static void change(String name) {
    name = "niu";
    System.out.println("change: "+ name); } output - "before: Steven change: niu after: StevenCopy the code

If the change method changes the same String that the reference should change, why does the change method not affect the String object of main?

It starts with the String class, which declares:

public final class String implements Serializable.Comparable<String>, CharSequence 
Copy the code

5, 1 Constant pool design

As you can see, strings are designed to be immutable and uninheritable, and strings are immutable and uninheritable (final modifier). The main reasons for this design are design considerations, efficiency, and security. String pooling is only possible if strings are immutable. The implementation of a string pool can save a lot of H-heap space at run time because different string variables all point to the same string in the pool. If a string object is allowed to change, this can lead to various logical errors, such as changing one object affecting another independent object. Strictly speaking, this constant pool idea is an optimization where all strings live in a constant pool, and String constant pools live in a runtime constant pool (which existed before JDK7 and has been moved to the heap in JDK7).

The presence of a string constant pool allows the JVM to improve performance and reduce memory overhead.

Using a String constant pool, whenever we use literals (String s= “***”;) When a string constant is created, the JVM first checks the string constant pool and assigns the address of the string object to reference S (which is in the Java stack) if the string already exists in the constant pool. If the string does not exist in the constant pool, the string is instantiated and placed in the constant pool, and the address of the string object is assigned to reference S (which is in the Java stack).

So when called in the change method:

 name = "niu";
Copy the code

Actually equivalent to calling:

 name= new String("niu");
Copy the code

Now the name in the change method refers to another String “niu”, and the name in the main method refers to the same String “Steven”.

It also confirms the theory that Java method parameter passing is value passing.

6, the conclusion

Java method parameters are passed by value (i.e. stack copy), regardless of whether the parameter is a basic type or an application type.

ids = ids.stream().filter(id -> ! isIdInvalid(id)).collect(Collectors.toList());Copy the code

After this code is processed, ids is redirected to a new List object, calculator.tolist (), so operating in the checkId method does not affect request.ids outside the method. Therefore, if you must use the filter method to remove illegal ids, you can use the following method:

request.setIds(getValidIds(request.getIds()));
Copy the code

Get the valid ID and return to reassign it to the parameter outside the method

private List<String> getValidIds(List<String> ids) throws CommonException {
    if (ids.size() > 200) {
        throw new CommonException(CELL_MAX_NUM);
    }
    returnids.stream().filter(id -> ! isIdInvalid(id)).collect(Collectors.toList()); }Copy the code