You can always trust String!

Posted by Rother2005 on Fri, 18 Feb 2022 12:17:21 +0100

String & Immutable

1. Concepts

String classes are modified by final, and similar final-modified classes include wrapper classes such as Integer, Double, and so on. As we learned in our second grade, final-modified classes are inheritable. In Java, final-modified classes are called "immutable classes". Curious classmates may ask, 1. Why do these classes need to be finalized? 2. Why can't you change it? What's the advantage of not changing? Let's take String as an example to explore.

2. immutable

If you read the source code for the String method, you know that the core of String is its member variable value

private final char value[];

All String methods revolve around this array of char types.

As we learned in second grade, final-modified member variables have several characteristics

  1. If you are modifying instance member variables, you need to initialize the member variables before the constructor finishes executing. If it is a static member variable, the initialization needs to be completed before the class initialization is complete.
  2. The value of a variable cannot be changed. If you modify the reference type, the value represents the memory address stored in the object. If it is a basic data type, the value of the variable cannot be changed.

value is a member variable of reference type, decorated with final, instance. This means that the array needs to be initialized when the String object is created. After initialization, the memory address of the object cannot be changed.

public class StringTest2 {
    private final char[] value;
    
    public StringTest2(){
        value = new char[]{1,2,3};
    }
    
    public void update(){
        value[2] = 100;
    }
}

Although the memory address of the value cannot be changed, it appears from the example above that the values in the array can be changed. But the engineer who designed the String 1. Privateize the value array, 2. No method is provided to modify the elements of the value array (no setter method), 3. The value of the value array has not been modified anywhere in the source code.

For example, String. The replace method, instead of actually modifying the value array, copies the value array and returns it after modifying it on a new array. The test code is as follows:

String str = "test";
String replace = str.replace('t', 'f');
System.out.println(replace);
System.out.println(str);

//output
fesf
test

Additionally, at the beginning of the article, the designer modifies the String class with final so that it cannot be inherited or have subclasses. It is also impossible to achieve polymorphism through inheritance, changing the implementation of methods in String. We can learn from the following examples

public class Son{
    @Override
    public String toString() {
        return "Ha ha ha";
    }
}

public void sout(String str){
        System.out.println(str);
}

We assume that the Son class is a subclass of String and that when the lower sout method is called, an object of type String needs to be passed in. Students who know polymorphism know that polymorphism is an object whose reference to the parent class points to the child class. We can use polymorphism to pass an instance of Son into the source method, and the result is a "haha" instead of an output String object value.

It shows how hard the engineer who designed the String has been, and in order to let us know, the String class should be immutable.

To avoid methods in a String being extended/modified, in order to ensure the immutability of the String, modify the String with final to indicate that it is immutable. This also answers the first question at the beginning of the article. Let's answer the second question: Why is it good to design a String as immutable?

3. Why String is designed to be immutable

  • security

    We can understand immutable security through the following examples.

    1. Instances of immutable classes as key values for HashMap
    class Student{
        private String name;
        private Integer id;
    
        public Student(String name, Integer id) {
            this.name = name;
            this.id = id;
        }
    
        //getter.. Length reason, omitted here
        //setter.. Length reason, omitted here
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Student)) return false;
            Student student = (Student) o;
            return Objects.equals(getName(), student.getName()) &&
                    Objects.equals(getId(), student.getId());
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(getName(), getId());
        }
    }
    
    public static void main(String[] args) {
            HashMap<String,Integer> test1 = new HashMap<>();
            HashMap<Student,Integer> test2 = new HashMap<>();
    
            test1.put("1",1);
            test1.put("2",1);
    
            Student student1 = new Student("thd",1);
            Student student2 = new Student("djw",2);
            test2.put(student1,1);
            test2.put(student2,2);
            student2.setName("thd");
            student2.setId(1);
    
            System.out.println(student1.hashCode());
            System.out.println(student2.hashCode());
            System.out.println(student1.equals(student2));
            System.out.println(test2.get(student2));
    }
    
    //output
    3559762
    3559762
    true
    1
    

    When the key value in HashMap is a variable class, change the student2 information to be the same as student1 if you follow the above actions. Through hashMap. The get method gets the value, resulting in an information mismatch error. (Therefore, immutable strings are often used in development as the key value for HashMap). Conversely, instances of immutable classes are stored in HashMap and are no longer modified externally in any way, thereby guaranteeing the uniqueness of the key.

    2. Thread security: Since information about immutable classes cannot be modified externally, there is no write operation on immutable classes in multiple threads, only read operation, so there is no thread security issue.

    3. Strings are often used as parameters in network and database connections. For example, the network connection address URL, file path Path. (I don't know much about this and will explain it later)

  • efficiency

    1. Based on String's immutability, Java proposed a pool of string constants. In actual development, we often need to operate on strings. Without a pool of string constants, there will be a lot of duplicate string objects in the system, which wastes a lot of memory space. With a pool of string constants, you can improve efficiency and reduce memory allocation for reuse. If a String is mutable, then there is no concept of a string constant, and a pool of string constants cannot be proposed. We see this through the following examples:

    String str = "123";
    str = "456";
    String str1 = "123";
    

    If the String is variable, the following is the picture:

    str is a reference in the stack to an object in the stack. If the String is variable, the value of the "123" object that str points to becomes "456", the same object before and after, with the same address unchanged. At this point str1 needs to point to an object with a value of "123", which cannot be found in the heap and can only be created again.

    But if String is immutable, the following is the picture:

    There is no longer a need to create new String objects, and string objects that have appeared in the program are stored in a pool of string constants for unified management.

    2. Privatization in String objects encapsulates a hash used to cache hashes, and the hash code for String objects is frequently used, such as in containers such as hashMap. String immutability guarantees hashCode uniqueness, so you can safely cache it. This is also a means of performance optimization, meaning you don't have to calculate a new hash code every time.

    /** Cache the hash code for the string */
        private int hash; // Default to 0
    

    3. jvm actually needs to check the security of this String object before each string change check. That is, through hashcode, when it is designed as an immutable object, it guarantees the uniqueness of hashcode for each change and increase, which can be safely operated.

4. Summary

1. How to make a class immutable

  • Modify the class with final so that it cannot be inherited
  • Privateize member variables
  • Modify member variables with final
  • Complete initialization of member variables in the constructor
  • Do not provide external methods to modify member variables
  • When a class contains mutable attributes, in order to prevent attributes from being modified, the getter method should not return a reference to the object but a new object containing the same content as the object
final class Student1{
    //immutable field
    private final String name;
    //mutable field
    private final Date date;

    public Student1(String name,Date date) {
        this.name = name;
        this.date = date;
    }

    public String getName(){
        return name;
    }

    public Date getDate(){
        return new Date(this.date.getTime());
    }
}

2. What's the benefit of immutability?

  • security
    1. Can be used as a key value for HashMap
    2. Thread Security
  • Efficient
    1. hashCode can be cached without recalculating each time it is used
    2. Cache pool can be implemented

Topics: Java string